Merge lp:~ltrager/maas/clean_api into lp:~maas-committers/maas/trunk
- clean_api
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Lee Trager | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 4714 | ||||
Proposed branch: | lp:~ltrager/maas/clean_api | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
5301 lines (+1199/-1153) 85 files modified
src/maasserver/api/account.py (+1/-1) src/maasserver/api/auth.py (+1/-1) src/maasserver/api/bcache.py (+15/-15) src/maasserver/api/bcache_cacheset.py (+14/-14) src/maasserver/api/blockdevices.py (+35/-35) src/maasserver/api/boot_resources.py (+1/-1) src/maasserver/api/boot_source_selections.py (+1/-1) src/maasserver/api/boot_sources.py (+1/-1) src/maasserver/api/commissioning_scripts.py (+3/-3) src/maasserver/api/devices.py (+34/-90) src/maasserver/api/dnsresourcerecords.py (+1/-1) src/maasserver/api/dnsresources.py (+1/-1) src/maasserver/api/doc.py (+1/-1) src/maasserver/api/doc_handler.py (+2/-2) src/maasserver/api/domains.py (+1/-1) src/maasserver/api/events.py (+1/-1) src/maasserver/api/fabrics.py (+1/-1) src/maasserver/api/fannetworks.py (+1/-1) src/maasserver/api/files.py (+1/-1) src/maasserver/api/interfaces.py (+2/-2) src/maasserver/api/ip_addresses.py (+1/-1) src/maasserver/api/license_keys.py (+1/-1) src/maasserver/api/logger.py (+1/-1) src/maasserver/api/maas.py (+1/-1) src/maasserver/api/machines.py (+91/-20) src/maasserver/api/networks.py (+1/-1) src/maasserver/api/nodes.py (+33/-8) src/maasserver/api/not_found.py (+1/-1) src/maasserver/api/pxeconfig.py (+1/-1) src/maasserver/api/rackcontrollers.py (+9/-12) src/maasserver/api/raid.py (+15/-15) src/maasserver/api/spaces.py (+1/-1) src/maasserver/api/ssh_keys.py (+1/-1) src/maasserver/api/ssl_keys.py (+1/-1) src/maasserver/api/subnets.py (+1/-1) src/maasserver/api/support.py (+1/-1) src/maasserver/api/tags.py (+1/-1) src/maasserver/api/tests/test_devices.py (+51/-2) src/maasserver/api/tests/test_doc.py (+11/-0) src/maasserver/api/tests/test_enlistment.py (+34/-4) src/maasserver/api/tests/test_machine.py (+2/-205) src/maasserver/api/tests/test_machines.py (+78/-55) src/maasserver/api/tests/test_node.py (+1/-161) src/maasserver/api/tests/test_nodes.py (+1/-31) src/maasserver/api/tests/test_rackcontroller.py (+26/-4) src/maasserver/api/users.py (+1/-1) src/maasserver/api/utils.py (+1/-1) src/maasserver/api/version.py (+1/-1) src/maasserver/api/volume_groups.py (+22/-20) src/maasserver/api/zones.py (+1/-1) src/maasserver/forms.py (+210/-144) src/maasserver/models/node.py (+5/-4) src/maasserver/models/tests/test_node.py (+7/-4) src/maasserver/preseed.py (+1/-1) src/maasserver/rpc/nodes.py (+10/-5) src/maasserver/rpc/regionservice.py (+2/-2) src/maasserver/rpc/tests/test_nodes.py (+49/-0) src/maasserver/rpc/tests/test_regionservice.py (+3/-1) src/maasserver/tests/test_forms_device.py (+5/-16) src/maasserver/tests/test_forms_helpers.py (+19/-8) src/maasserver/tests/test_forms_machine.py (+40/-155) src/maasserver/tests/test_forms_machinewithmacaddresses.py (+14/-14) src/maasserver/tests/test_forms_node.py (+204/-0) src/maasserver/websockets/handlers/controller.py (+2/-2) src/maasserver/websockets/handlers/machine.py (+3/-3) src/maasserver/websockets/handlers/tests/test_controller.py (+3/-3) src/maasserver/websockets/handlers/tests/test_machine.py (+3/-3) src/maasserver/websockets/tests/test_base.py (+7/-7) src/provisioningserver/drivers/hardware/msftocs.py (+3/-2) src/provisioningserver/drivers/hardware/seamicro.py (+5/-3) src/provisioningserver/drivers/hardware/tests/test_msftocs.py (+3/-2) src/provisioningserver/drivers/hardware/tests/test_seamicro.py (+3/-2) src/provisioningserver/drivers/hardware/tests/test_ucsm.py (+3/-2) src/provisioningserver/drivers/hardware/tests/test_virsh.py (+7/-6) src/provisioningserver/drivers/hardware/ucsm.py (+4/-2) src/provisioningserver/drivers/hardware/virsh.py (+5/-3) src/provisioningserver/drivers/hardware/vmware.py (+5/-5) src/provisioningserver/drivers/power/mscm.py (+3/-2) src/provisioningserver/drivers/power/tests/test_mscm.py (+3/-2) src/provisioningserver/rpc/cluster.py (+1/-0) src/provisioningserver/rpc/clusterservice.py (+11/-9) src/provisioningserver/rpc/region.py (+1/-0) src/provisioningserver/rpc/tests/test_clusterservice.py (+35/-7) src/provisioningserver/rpc/tests/test_utils.py (+6/-3) src/provisioningserver/rpc/utils.py (+5/-3) |
||||
To merge this branch: | bzr merge lp:~ltrager/maas/clean_api | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andres Rodriguez (community) | Approve | ||
Blake Rouse (community) | Approve | ||
Review via email: mp+287759@code.launchpad.net |
Commit message
Clean up the API
Description of the change
Sorry for posting such a large review, much of this is just renames and moving some code around.
* Update the API docs to use machine consistently where only a machine node type can be used.
* Clean up duplicated tests. A number of the tests in TestNodeAPI were checking the output of a given machine. As the output code is handled by the Machine API this is already tested in TestMachineAPI.
* Limit the amount of items outputted for rack controllers, see http://
* Base Device and RackController API off of Node API.
* I created an update method for node which now rack controller uses as well.
* To facilitate the new base update method I split NodeForm into NodeForm and MachineForm. NodeForm is a basic form which allows setting general options(hostname, domain, disable_ipv4, swap_size). DeviceForm and MachineForm inherit from NodeForm and expand on those options. This allows us to have a consistent set of options throughout all node types while removing duplicated code.
* The API has been updated so that all node types output only the hostname in the hostname field. The domain and fqdn fields have been added to the output as well.
* Domains can now be specified when creating devices, nodes, or adding chassis. The domain field can be changed with any end points update method.
Andres Rodriguez (andreserl) : | # |
Lee Trager (ltrager) wrote : | # |
I apologize for such a huge branch, I kept adding just one more thing to get into alpha 1 which caused this to blow up. I'll make my branches smaller in the future.
I answered this inline but the reason the Device API has a number of methods removed is because its now based on the NodesHandler class. This brings consistency to the specifiers we can pass to our read methods and removed a bunch of duplicated code.
Preview Diff
1 | === modified file 'src/maasserver/api/account.py' |
2 | --- src/maasserver/api/account.py 2015-12-01 18:12:59 +0000 |
3 | +++ src/maasserver/api/account.py 2016-03-02 18:21:51 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | """API handler: `Account`.""" |
10 | |
11 | === modified file 'src/maasserver/api/auth.py' |
12 | --- src/maasserver/api/auth.py 2015-12-01 18:12:59 +0000 |
13 | +++ src/maasserver/api/auth.py 2016-03-02 18:21:51 +0000 |
14 | @@ -1,4 +1,4 @@ |
15 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
16 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
17 | # GNU Affero General Public License version 3 (see the file LICENSE). |
18 | |
19 | """OAuth authentication for the various APIs.""" |
20 | |
21 | === modified file 'src/maasserver/api/bcache.py' |
22 | --- src/maasserver/api/bcache.py 2015-12-16 00:01:56 +0000 |
23 | +++ src/maasserver/api/bcache.py 2016-03-02 18:21:51 +0000 |
24 | @@ -1,4 +1,4 @@ |
25 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
26 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
27 | # GNU Affero General Public License version 3 (see the file LICENSE). |
28 | |
29 | """API handlers: `Bcache`.""" |
30 | @@ -38,7 +38,7 @@ |
31 | |
32 | |
33 | class BcachesHandler(OperationsHandler): |
34 | - """Manage bcache devices on a node.""" |
35 | + """Manage bcache devices on a machine.""" |
36 | api_doc_section_name = "Bcache Devices" |
37 | update = delete = None |
38 | fields = DISPLAYED_BCACHE_FIELDS |
39 | @@ -49,7 +49,7 @@ |
40 | return ('bcache_devices_handler', ["system_id"]) |
41 | |
42 | def read(self, request, system_id): |
43 | - """List all bcache devices belonging to node. |
44 | + """List all bcache devices belonging to a machine. |
45 | |
46 | Returns 404 if the machine is not found. |
47 | """ |
48 | @@ -77,7 +77,7 @@ |
49 | system_id, request.user, NODE_PERMISSION.ADMIN) |
50 | if machine.status != NODE_STATUS.READY: |
51 | raise NodeStateViolation( |
52 | - "Cannot create Bcache because node is not Ready.") |
53 | + "Cannot create Bcache because the machine is not Ready.") |
54 | form = CreateBcacheForm(machine, data=request.data) |
55 | if form.is_valid(): |
56 | return form.save() |
57 | @@ -86,7 +86,7 @@ |
58 | |
59 | |
60 | class BcacheHandler(OperationsHandler): |
61 | - """Manage bcache device on a node.""" |
62 | + """Manage bcache device on a machine.""" |
63 | api_doc_section_name = "Bcache Device" |
64 | create = None |
65 | model = Bcache |
66 | @@ -123,30 +123,30 @@ |
67 | return bcache.get_bcache_backing_filesystem().get_parent() |
68 | |
69 | def read(self, request, system_id, bcache_id): |
70 | - """Read bcache device on node. |
71 | + """Read bcache device on a machine. |
72 | |
73 | - Returns 404 if the node or bcache is not found. |
74 | + Returns 404 if the machine or bcache is not found. |
75 | """ |
76 | return Bcache.objects.get_object_or_404( |
77 | system_id, bcache_id, request.user, NODE_PERMISSION.VIEW) |
78 | |
79 | def delete(self, request, system_id, bcache_id): |
80 | - """Delete bcache on node. |
81 | + """Delete bcache on a machine. |
82 | |
83 | - Returns 404 if the node or bcache is not found. |
84 | - Returns 409 if the node is not Ready. |
85 | + Returns 404 if the machine or bcache is not found. |
86 | + Returns 409 if the machine is not Ready. |
87 | """ |
88 | bcache = Bcache.objects.get_object_or_404( |
89 | system_id, bcache_id, request.user, NODE_PERMISSION.ADMIN) |
90 | node = bcache.get_node() |
91 | if node.status != NODE_STATUS.READY: |
92 | raise NodeStateViolation( |
93 | - "Cannot delete Bcache because the node is not Ready.") |
94 | + "Cannot delete Bcache because the machine is not Ready.") |
95 | bcache.delete() |
96 | return rc.DELETED |
97 | |
98 | def update(self, request, system_id, bcache_id): |
99 | - """Delete bcache on node. |
100 | + """Delete bcache on a machine. |
101 | |
102 | :param name: Name of the Bcache. |
103 | :param uuid: UUID of the Bcache. |
104 | @@ -158,15 +158,15 @@ |
105 | Specifying both a device and a partition for a given role (cache or |
106 | backing) is not allowed. |
107 | |
108 | - Returns 404 if the node or the bcache is not found. |
109 | - Returns 409 if the node is not Ready. |
110 | + Returns 404 if the machine or the bcache is not found. |
111 | + Returns 409 if the machine is not Ready. |
112 | """ |
113 | bcache = Bcache.objects.get_object_or_404( |
114 | system_id, bcache_id, request.user, NODE_PERMISSION.ADMIN) |
115 | node = bcache.get_node() |
116 | if node.status != NODE_STATUS.READY: |
117 | raise NodeStateViolation( |
118 | - "Cannot update Bcache because the node is not Ready.") |
119 | + "Cannot update Bcache because the machine is not Ready.") |
120 | form = UpdateBcacheForm(bcache, data=request.data) |
121 | if form.is_valid(): |
122 | return form.save() |
123 | |
124 | === modified file 'src/maasserver/api/bcache_cacheset.py' |
125 | --- src/maasserver/api/bcache_cacheset.py 2015-12-16 00:01:56 +0000 |
126 | +++ src/maasserver/api/bcache_cacheset.py 2016-03-02 18:21:51 +0000 |
127 | @@ -1,4 +1,4 @@ |
128 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
129 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
130 | # GNU Affero General Public License version 3 (see the file LICENSE). |
131 | |
132 | """API handlers: `CacheSet`.""" |
133 | @@ -32,7 +32,7 @@ |
134 | |
135 | |
136 | class BcacheCacheSetsHandler(OperationsHandler): |
137 | - """Manage bcache cache sets on a node.""" |
138 | + """Manage bcache cache sets on a machine.""" |
139 | api_doc_section_name = "Bcache Cache Sets" |
140 | update = delete = None |
141 | fields = DISPLAYED_CACHE_SET_FIELDS |
142 | @@ -43,7 +43,7 @@ |
143 | return ('bcache_cache_sets_handler', ["system_id"]) |
144 | |
145 | def read(self, request, system_id): |
146 | - """List all bcache cache sets belonging to node. |
147 | + """List all bcache cache sets belonging to a machine. |
148 | |
149 | Returns 404 if the machine is not found. |
150 | """ |
151 | @@ -75,7 +75,7 @@ |
152 | |
153 | |
154 | class BcacheCacheSetHandler(OperationsHandler): |
155 | - """Manage bcache cache set on a node.""" |
156 | + """Manage bcache cache set on a machine.""" |
157 | api_doc_section_name = "Bcache Cache Set" |
158 | create = None |
159 | model = CacheSet |
160 | @@ -99,26 +99,26 @@ |
161 | return cache_set.get_device() |
162 | |
163 | def read(self, request, system_id, cache_set_id): |
164 | - """Read bcache cache set on node. |
165 | + """Read bcache cache set on a machine. |
166 | |
167 | - Returns 404 if the node or cache set is not found. |
168 | + Returns 404 if the machine or cache set is not found. |
169 | """ |
170 | return CacheSet.objects.get_cache_set_or_404( |
171 | system_id, cache_set_id, request.user, NODE_PERMISSION.VIEW) |
172 | |
173 | def delete(self, request, system_id, cache_set_id): |
174 | - """Delete cache set on node. |
175 | + """Delete cache set on a machine. |
176 | |
177 | Returns 400 if the cache set is in use. |
178 | - Returns 404 if the node or cache set is not found. |
179 | - Returns 409 if the node is not Ready. |
180 | + Returns 404 if the machine or cache set is not found. |
181 | + Returns 409 if the machine is not Ready. |
182 | """ |
183 | cache_set = CacheSet.objects.get_cache_set_or_404( |
184 | system_id, cache_set_id, request.user, NODE_PERMISSION.ADMIN) |
185 | node = cache_set.get_node() |
186 | if node.status != NODE_STATUS.READY: |
187 | raise NodeStateViolation( |
188 | - "Cannot delete cache set because the node is not Ready.") |
189 | + "Cannot delete cache set because the machine is not Ready.") |
190 | if cache_set.filesystemgroup_set.exists(): |
191 | raise MAASAPIBadRequest( |
192 | "Cannot delete cache set; it's currently in use.") |
193 | @@ -127,22 +127,22 @@ |
194 | return rc.DELETED |
195 | |
196 | def update(self, request, system_id, cache_set_id): |
197 | - """Delete bcache on node. |
198 | + """Delete bcache on a machine. |
199 | |
200 | :param cache_device: Cache block device to replace current one. |
201 | :param cache_partition: Cache partition to replace current one. |
202 | |
203 | Specifying both a cache_device and a cache_partition is not allowed. |
204 | |
205 | - Returns 404 if the node or the cache set is not found. |
206 | - Returns 409 if the node is not Ready. |
207 | + Returns 404 if the machine or the cache set is not found. |
208 | + Returns 409 if the machine is not Ready. |
209 | """ |
210 | cache_set = CacheSet.objects.get_cache_set_or_404( |
211 | system_id, cache_set_id, request.user, NODE_PERMISSION.ADMIN) |
212 | node = cache_set.get_node() |
213 | if node.status != NODE_STATUS.READY: |
214 | raise NodeStateViolation( |
215 | - "Cannot update cache set because the node is not Ready.") |
216 | + "Cannot update cache set because the machine is not Ready.") |
217 | form = UpdateCacheSetForm(cache_set, data=request.data) |
218 | if form.is_valid(): |
219 | return form.save() |
220 | |
221 | === modified file 'src/maasserver/api/blockdevices.py' |
222 | --- src/maasserver/api/blockdevices.py 2016-03-01 16:02:44 +0000 |
223 | +++ src/maasserver/api/blockdevices.py 2016-03-02 18:21:51 +0000 |
224 | @@ -1,4 +1,4 @@ |
225 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
226 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
227 | # GNU Affero General Public License version 3 (see the file LICENSE). |
228 | |
229 | """API handlers: `BlockDevice`.""" |
230 | @@ -61,16 +61,16 @@ |
231 | node, user, operation): |
232 | if node.status not in [NODE_STATUS.READY, NODE_STATUS.ALLOCATED]: |
233 | raise NodeStateViolation( |
234 | - "Cannot %s block device because the node is not Ready " |
235 | + "Cannot %s block device because the machine is not Ready " |
236 | "or Allocated." % operation) |
237 | if node.status == NODE_STATUS.READY and not user.is_superuser: |
238 | raise PermissionDenied( |
239 | "Cannot %s block device because you don't have the " |
240 | - "permissions on a Ready node." % operation) |
241 | + "permissions on a Ready machine." % operation) |
242 | |
243 | |
244 | class BlockDevicesHandler(OperationsHandler): |
245 | - """Manage block devices on a node.""" |
246 | + """Manage block devices on a machine.""" |
247 | api_doc_section_name = "Block devices" |
248 | replace = update = delete = None |
249 | fields = DISPLAYED_BLOCKDEVICE_FIELDS |
250 | @@ -80,7 +80,7 @@ |
251 | return ('blockdevices_handler', ["system_id"]) |
252 | |
253 | def read(self, request, system_id): |
254 | - """List all block devices belonging to node. |
255 | + """List all block devices belonging to a machine. |
256 | |
257 | Returns 404 if the machine is not found. |
258 | """ |
259 | @@ -113,7 +113,7 @@ |
260 | |
261 | |
262 | class BlockDeviceHandler(OperationsHandler): |
263 | - """Manage a block device on a node.""" |
264 | + """Manage a block device on a machine.""" |
265 | api_doc_section_name = "Block device" |
266 | create = replace = None |
267 | model = BlockDevice |
268 | @@ -203,29 +203,29 @@ |
269 | def read(self, request, system_id, device_id): |
270 | """Read block device on node. |
271 | |
272 | - Returns 404 if the node or block device is not found. |
273 | + Returns 404 if the machine or block device is not found. |
274 | """ |
275 | return BlockDevice.objects.get_block_device_or_404( |
276 | system_id, device_id, request.user, NODE_PERMISSION.VIEW) |
277 | |
278 | def delete(self, request, system_id, device_id): |
279 | - """Delete block device on node. |
280 | + """Delete block device on a machine. |
281 | |
282 | - Returns 404 if the node or block device is not found. |
283 | + Returns 404 if the machine or block device is not found. |
284 | Returns 403 if the user is not allowed to delete the block device. |
285 | - Returns 409 if the node is not Ready. |
286 | + Returns 409 if the machine is not Ready. |
287 | """ |
288 | device = BlockDevice.objects.get_block_device_or_404( |
289 | system_id, device_id, request.user, NODE_PERMISSION.ADMIN) |
290 | node = device.get_node() |
291 | if node.status != NODE_STATUS.READY: |
292 | raise NodeStateViolation( |
293 | - "Cannot delete block device because the node is not Ready.") |
294 | + "Cannot delete block device because the machine is not Ready.") |
295 | device.delete() |
296 | return rc.DELETED |
297 | |
298 | def update(self, request, system_id, device_id): |
299 | - """Update block device on node. |
300 | + """Update block device on a machine. |
301 | |
302 | Fields for physical block device: |
303 | :param name: Name of the block device. |
304 | @@ -243,16 +243,16 @@ |
305 | :param size: Size of the block device. (Only allowed for logical \ |
306 | volumes.) |
307 | |
308 | - Returns 404 if the node or block device is not found. |
309 | + Returns 404 if the machine or block device is not found. |
310 | Returns 403 if the user is not allowed to update the block device. |
311 | - Returns 409 if the node is not Ready. |
312 | + Returns 409 if the machine is not Ready. |
313 | """ |
314 | device = BlockDevice.objects.get_block_device_or_404( |
315 | system_id, device_id, request.user, NODE_PERMISSION.ADMIN) |
316 | node = device.get_node() |
317 | if node.status != NODE_STATUS.READY: |
318 | raise NodeStateViolation( |
319 | - "Cannot update block device because the node is not Ready.") |
320 | + "Cannot update block device because the machine is not Ready.") |
321 | if device.type == 'physical': |
322 | form = UpdatePhysicalBlockDeviceForm( |
323 | instance=device, data=request.data) |
324 | @@ -269,40 +269,40 @@ |
325 | |
326 | @operation(idempotent=True) |
327 | def add_tag(self, request, system_id, device_id): |
328 | - """Add a tag to block device on node. |
329 | + """Add a tag to block device on a machine. |
330 | |
331 | :param tag: The tag being added. |
332 | |
333 | - Returns 404 if the node or block device is not found. |
334 | + Returns 404 if the machine or block device is not found. |
335 | Returns 403 if the user is not allowed to update the block device. |
336 | - Returns 409 if the node is not Ready. |
337 | + Returns 409 if the machine is not Ready. |
338 | """ |
339 | device = BlockDevice.objects.get_block_device_or_404( |
340 | system_id, device_id, request.user, NODE_PERMISSION.ADMIN) |
341 | node = device.get_node() |
342 | if node.status != NODE_STATUS.READY: |
343 | raise NodeStateViolation( |
344 | - "Cannot update block device because the node is not Ready.") |
345 | + "Cannot update block device because the machine is not Ready.") |
346 | device.add_tag(get_mandatory_param(request.GET, 'tag')) |
347 | device.save() |
348 | return device |
349 | |
350 | @operation(idempotent=True) |
351 | def remove_tag(self, request, system_id, device_id): |
352 | - """Remove a tag from block device on node. |
353 | + """Remove a tag from block device on a machine. |
354 | |
355 | :param tag: The tag being removed. |
356 | |
357 | - Returns 404 if the node or block device is not found. |
358 | + Returns 404 if the machine or block device is not found. |
359 | Returns 403 if the user is not allowed to update the block device. |
360 | - Returns 409 if the node is not Ready. |
361 | + Returns 409 if the machine is not Ready. |
362 | """ |
363 | device = BlockDevice.objects.get_block_device_or_404( |
364 | system_id, device_id, request.user, NODE_PERMISSION.ADMIN) |
365 | node = device.get_node() |
366 | if node.status != NODE_STATUS.READY: |
367 | raise NodeStateViolation( |
368 | - "Cannot update block device because the node is not Ready.") |
369 | + "Cannot update block device because the machine is not Ready.") |
370 | device.remove_tag(get_mandatory_param(request.GET, 'tag')) |
371 | device.save() |
372 | return device |
373 | @@ -316,8 +316,8 @@ |
374 | |
375 | Returns 403 when the user doesn't have the ability to format the \ |
376 | block device. |
377 | - Returns 404 if the node or block device is not found. |
378 | - Returns 409 if the node is not Ready or Allocated. |
379 | + Returns 404 if the machine or block device is not found. |
380 | + Returns 409 if the machine is not Ready or Allocated. |
381 | """ |
382 | device = BlockDevice.objects.get_block_device_or_404( |
383 | system_id, device_id, request.user, NODE_PERMISSION.EDIT) |
384 | @@ -338,8 +338,8 @@ |
385 | or part of a filesystem group. |
386 | Returns 403 when the user doesn't have the ability to unformat the \ |
387 | block device. |
388 | - Returns 404 if the node or block device is not found. |
389 | - Returns 409 if the node is not Ready or Allocated. |
390 | + Returns 404 if the machine or block device is not found. |
391 | + Returns 409 if the machine is not Ready or Allocated. |
392 | """ |
393 | device = BlockDevice.objects.get_block_device_or_404( |
394 | system_id, device_id, request.user, NODE_PERMISSION.EDIT) |
395 | @@ -371,8 +371,8 @@ |
396 | |
397 | Returns 403 when the user doesn't have the ability to mount the \ |
398 | block device. |
399 | - Returns 404 if the node or block device is not found. |
400 | - Returns 409 if the node is not Ready or Allocated. |
401 | + Returns 404 if the machine or block device is not found. |
402 | + Returns 409 if the machine is not Ready or Allocated. |
403 | """ |
404 | device = BlockDevice.objects.get_block_device_or_404( |
405 | system_id, device_id, request.user, NODE_PERMISSION.EDIT) |
406 | @@ -394,8 +394,8 @@ |
407 | mounted. |
408 | Returns 403 when the user doesn't have the ability to unmount the \ |
409 | block device. |
410 | - Returns 404 if the node or block device is not found. |
411 | - Returns 409 if the node is not Ready or Allocated. |
412 | + Returns 404 if the machine or block device is not found. |
413 | + Returns 409 if the machine is not Ready or Allocated. |
414 | """ |
415 | device = BlockDevice.objects.get_block_device_or_404( |
416 | system_id, device_id, request.user, NODE_PERMISSION.EDIT) |
417 | @@ -414,19 +414,19 @@ |
418 | |
419 | @operation(idempotent=False) |
420 | def set_boot_disk(self, request, system_id, device_id): |
421 | - """Set this block device as the boot disk for the node. |
422 | + """Set this block device as the boot disk for the machine. |
423 | |
424 | Returns 400 if the block device is a virtual block device. |
425 | - Returns 404 if the node or block device is not found. |
426 | + Returns 404 if the machine or block device is not found. |
427 | Returns 403 if the user is not allowed to update the block device. |
428 | - Returns 409 if the node is not Ready or Allocated. |
429 | + Returns 409 if the machine is not Ready or Allocated. |
430 | """ |
431 | device = BlockDevice.objects.get_block_device_or_404( |
432 | system_id, device_id, request.user, NODE_PERMISSION.ADMIN) |
433 | node = device.get_node() |
434 | if node.status != NODE_STATUS.READY: |
435 | raise NodeStateViolation( |
436 | - "Cannot set as boot disk because the node is not Ready.") |
437 | + "Cannot set as boot disk because the machine is not Ready.") |
438 | if not isinstance(device, PhysicalBlockDevice): |
439 | raise MAASAPIBadRequest( |
440 | "Cannot set a %s block device as the boot disk." % device.type) |
441 | |
442 | === modified file 'src/maasserver/api/boot_resources.py' |
443 | --- src/maasserver/api/boot_resources.py 2016-02-19 08:38:36 +0000 |
444 | +++ src/maasserver/api/boot_resources.py 2016-03-02 18:21:51 +0000 |
445 | @@ -1,4 +1,4 @@ |
446 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
447 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
448 | # GNU Affero General Public License version 3 (see the file LICENSE). |
449 | |
450 | """API handlers: `BootResouce`.""" |
451 | |
452 | === modified file 'src/maasserver/api/boot_source_selections.py' |
453 | --- src/maasserver/api/boot_source_selections.py 2015-12-01 18:12:59 +0000 |
454 | +++ src/maasserver/api/boot_source_selections.py 2016-03-02 18:21:51 +0000 |
455 | @@ -1,4 +1,4 @@ |
456 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
457 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
458 | # GNU Affero General Public License version 3 (see the file LICENSE). |
459 | |
460 | """API handlers: `BootSourceSelection`""" |
461 | |
462 | === modified file 'src/maasserver/api/boot_sources.py' |
463 | --- src/maasserver/api/boot_sources.py 2015-12-11 20:45:50 +0000 |
464 | +++ src/maasserver/api/boot_sources.py 2016-03-02 18:21:51 +0000 |
465 | @@ -1,4 +1,4 @@ |
466 | -# Copyright 2014 Canonical Ltd. This software is licensed under the |
467 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
468 | # GNU Affero General Public License version 3 (see the file LICENSE). |
469 | |
470 | """API handlers: `BootSource`.""" |
471 | |
472 | === modified file 'src/maasserver/api/commissioning_scripts.py' |
473 | --- src/maasserver/api/commissioning_scripts.py 2015-12-01 18:12:59 +0000 |
474 | +++ src/maasserver/api/commissioning_scripts.py 2016-03-02 18:21:51 +0000 |
475 | @@ -1,4 +1,4 @@ |
476 | -# Copyright 2014 Canonical Ltd. This software is licensed under the |
477 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
478 | # GNU Affero General Public License version 3 (see the file LICENSE). |
479 | |
480 | """API handlers: `CommissioningScript`.""" |
481 | @@ -49,7 +49,7 @@ |
482 | so opens you up to risks w.r.t. encoding and ordering. The name must |
483 | not contain any whitespace, quotes, or apostrophes. |
484 | |
485 | - A commissioning node will run each of the scripts in lexicographical |
486 | + A commissioning machine will run each of the scripts in lexicographical |
487 | order. There are no promises about how non-ASCII characters are |
488 | sorted, or even how upper-case letters are sorted relative to |
489 | lower-case letters. So where ordering matters, use unique numbers. |
490 | @@ -61,7 +61,7 @@ |
491 | script should be ASCII text to avoid any confusion over encoding. But |
492 | in some cases a commissioning script might consist of a binary tool |
493 | provided by a hardware vendor. Either way, the script gets passed to |
494 | - the commissioning node in the exact form in which it was uploaded. |
495 | + the commissioning machine in the exact form in which it was uploaded. |
496 | |
497 | :param name: Unique identifying name for the script. Names should |
498 | follow the pattern of "25-burn-in-hard-disk" (all ASCII, and with |
499 | |
500 | === modified file 'src/maasserver/api/devices.py' |
501 | --- src/maasserver/api/devices.py 2016-02-18 00:34:58 +0000 |
502 | +++ src/maasserver/api/devices.py 2016-03-02 18:21:51 +0000 |
503 | @@ -1,4 +1,4 @@ |
504 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
505 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
506 | # GNU Affero General Public License version 3 (see the file LICENSE). |
507 | |
508 | __all__ = [ |
509 | @@ -7,17 +7,13 @@ |
510 | ] |
511 | |
512 | from maasserver.api.logger import maaslog |
513 | -from maasserver.api.support import ( |
514 | - operation, |
515 | - OperationsHandler, |
516 | -) |
517 | -from maasserver.api.utils import get_optional_list |
518 | -from maasserver.enum import ( |
519 | - NODE_PERMISSION, |
520 | - NODE_TYPE_CHOICES, |
521 | -) |
522 | +from maasserver.api.nodes import ( |
523 | + NodeHandler, |
524 | + NodesHandler, |
525 | +) |
526 | +from maasserver.api.support import operation |
527 | +from maasserver.enum import NODE_PERMISSION |
528 | from maasserver.exceptions import MAASAPIValidationError |
529 | -from maasserver.fields import MAC_RE |
530 | from maasserver.forms import ( |
531 | DeviceForm, |
532 | DeviceWithMACsForm, |
533 | @@ -29,6 +25,8 @@ |
534 | DISPLAYED_DEVICE_FIELDS = ( |
535 | 'system_id', |
536 | 'hostname', |
537 | + 'domain', |
538 | + 'fqdn', |
539 | 'owner', |
540 | 'macaddress_set', |
541 | 'parent', |
542 | @@ -40,7 +38,7 @@ |
543 | ) |
544 | |
545 | |
546 | -class DeviceHandler(OperationsHandler): |
547 | +class DeviceHandler(NodeHandler): |
548 | """Manage an individual device. |
549 | |
550 | The device is identified by its system_id. |
551 | @@ -59,52 +57,28 @@ |
552 | else: |
553 | return node.parent.system_id |
554 | |
555 | - @classmethod |
556 | - def hostname(handler, node): |
557 | - """Override the 'hostname' field so that it returns the FQDN.""" |
558 | - return node.fqdn |
559 | - |
560 | - @classmethod |
561 | - def owner(handler, node): |
562 | - """Override 'owner' so it emits the owner's name rather than a |
563 | - full nested user object.""" |
564 | - if node.owner is None: |
565 | - return None |
566 | - return node.owner.username |
567 | - |
568 | - @classmethod |
569 | - def macaddress_set(handler, device): |
570 | - return [ |
571 | - {"mac_address": "%s" % interface.mac_address} |
572 | - for interface in device.interface_set.all() |
573 | - if interface.mac_address |
574 | - ] |
575 | - |
576 | - @classmethod |
577 | - def node_type_name(handler, node): |
578 | - return NODE_TYPE_CHOICES[node.node_type][1] |
579 | - |
580 | - def read(self, request, system_id): |
581 | - """Read a specific device. |
582 | - |
583 | - Returns 404 if the device is not found. |
584 | - """ |
585 | - return Device.objects.get_node_or_404( |
586 | - system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW) |
587 | - |
588 | def update(self, request, system_id): |
589 | """Update a specific device. |
590 | |
591 | :param hostname: The new hostname for this device. |
592 | + :type hostname: unicode |
593 | + |
594 | + :param domain: The domain for this device. |
595 | + :type domain: unicode |
596 | + |
597 | :param parent: Optional system_id to indicate this device's parent. |
598 | If the parent is already set and this parameter is omitted, |
599 | the parent will be unchanged. |
600 | - :type hostname: unicode |
601 | + :type parent: unicode |
602 | + |
603 | + :param zone: Name of a valid physical zone in which to place this |
604 | + node. |
605 | + :type zone: unicode |
606 | |
607 | Returns 404 if the device is not found. |
608 | Returns 403 if the user does not have permission to update the device. |
609 | """ |
610 | - device = Device.objects.get_node_or_404( |
611 | + device = self.model.objects.get_node_or_404( |
612 | system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT) |
613 | form = DeviceForm(data=request.data, instance=device) |
614 | |
615 | @@ -120,7 +94,7 @@ |
616 | Returns 403 if the user does not have permission to delete the device. |
617 | Returns 204 if the device is successfully deleted. |
618 | """ |
619 | - device = Device.objects.get_node_or_404( |
620 | + device = self.model.objects.get_node_or_404( |
621 | system_id=system_id, user=request.user, |
622 | perm=NODE_PERMISSION.EDIT) |
623 | device.delete() |
624 | @@ -139,18 +113,28 @@ |
625 | return ('device_handler', (device_system_id,)) |
626 | |
627 | |
628 | -class DevicesHandler(OperationsHandler): |
629 | +class DevicesHandler(NodesHandler): |
630 | """Manage the collection of all the devices in the MAAS.""" |
631 | api_doc_section_name = "Devices" |
632 | update = delete = None |
633 | + base_model = Device |
634 | |
635 | @operation(idempotent=False) |
636 | def create(self, request): |
637 | """Create a new device. |
638 | |
639 | + :param hostname: A hostname. If not given, one will be generated. |
640 | + :type hostname: unicode |
641 | + |
642 | + :param domain: The domain of the device. If not given the default |
643 | + domain is used. |
644 | + :type domain: unicode |
645 | + |
646 | :param mac_addresses: One or more MAC addresses for the device. |
647 | - :param hostname: A hostname. If not given, one will be generated. |
648 | + :type mac_addresses: unicode |
649 | + |
650 | :param parent: The system id of the parent. Optional. |
651 | + :type parent: unicode |
652 | """ |
653 | form = DeviceWithMACsForm(data=request.data, request=request) |
654 | if form.is_valid(): |
655 | @@ -163,46 +147,6 @@ |
656 | else: |
657 | raise MAASAPIValidationError(form.errors) |
658 | |
659 | - def read(self, request): |
660 | - """List devices visible to the user, optionally filtered by criteria. |
661 | - |
662 | - :param hostname: An optional list of hostnames. Only devices with |
663 | - matching hostnames will be returned. |
664 | - :type hostname: iterable |
665 | - :param mac_address: An optional list of MAC addresses. Only |
666 | - devices with matching MAC addresses will be returned. |
667 | - :type mac_address: iterable |
668 | - :param id: An optional list of system ids. Only devices with |
669 | - matching system ids will be returned. |
670 | - :type id: iterable |
671 | - """ |
672 | - # Get filters from request. |
673 | - match_ids = get_optional_list(request.GET, 'id') |
674 | - match_macs = get_optional_list(request.GET, 'mac_address') |
675 | - if match_macs is not None: |
676 | - invalid_macs = [ |
677 | - mac for mac in match_macs if MAC_RE.match(mac) is None] |
678 | - if len(invalid_macs) != 0: |
679 | - raise MAASAPIValidationError( |
680 | - "Invalid MAC address(es): %s" % ", ".join(invalid_macs)) |
681 | - |
682 | - # Fetch nodes and apply filters. |
683 | - devices = Device.objects.get_nodes( |
684 | - request.user, NODE_PERMISSION.VIEW, ids=match_ids) |
685 | - if match_macs is not None: |
686 | - devices = devices.filter(interface__mac_address__in=match_macs) |
687 | - match_hostnames = get_optional_list(request.GET, 'hostname') |
688 | - if match_hostnames is not None: |
689 | - devices = devices.filter(hostname__in=match_hostnames) |
690 | - |
691 | - # Prefetch related objects that are needed for rendering the result. |
692 | - devices = devices.prefetch_related('interface_set__node') |
693 | - devices = devices.prefetch_related('interface_set__ip_addresses') |
694 | - devices = devices.prefetch_related('tags') |
695 | - devices = devices.prefetch_related('zone') |
696 | - devices = devices.prefetch_related('domain') |
697 | - return devices.order_by('id') |
698 | - |
699 | @classmethod |
700 | def resource_uri(cls, *args, **kwargs): |
701 | return ('devices_handler', []) |
702 | |
703 | === modified file 'src/maasserver/api/dnsresourcerecords.py' |
704 | --- src/maasserver/api/dnsresourcerecords.py 2016-02-11 18:11:40 +0000 |
705 | +++ src/maasserver/api/dnsresourcerecords.py 2016-03-02 18:21:51 +0000 |
706 | @@ -1,4 +1,4 @@ |
707 | -# Copyright 2015,2016 Canonical Ltd. This software is licensed under the |
708 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
709 | # GNU Affero General Public License version 3 (see the file LICENSE). |
710 | |
711 | """API handlers: `DNSData`.""" |
712 | |
713 | === modified file 'src/maasserver/api/dnsresources.py' |
714 | --- src/maasserver/api/dnsresources.py 2016-01-25 15:11:21 +0000 |
715 | +++ src/maasserver/api/dnsresources.py 2016-03-02 18:21:51 +0000 |
716 | @@ -1,4 +1,4 @@ |
717 | -# Copyright 2015,2016 Canonical Ltd. This software is licensed under the |
718 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
719 | # GNU Affero General Public License version 3 (see the file LICENSE). |
720 | |
721 | """API handlers: `DNSResource`.""" |
722 | |
723 | === modified file 'src/maasserver/api/doc.py' |
724 | --- src/maasserver/api/doc.py 2016-02-10 20:24:35 +0000 |
725 | +++ src/maasserver/api/doc.py 2016-03-02 18:21:51 +0000 |
726 | @@ -1,4 +1,4 @@ |
727 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
728 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
729 | # GNU Affero General Public License version 3 (see the file LICENSE). |
730 | |
731 | """Utilities to help document/describe the public facing API.""" |
732 | |
733 | === modified file 'src/maasserver/api/doc_handler.py' |
734 | --- src/maasserver/api/doc_handler.py 2016-02-11 15:06:36 +0000 |
735 | +++ src/maasserver/api/doc_handler.py 2016-03-02 18:21:51 +0000 |
736 | @@ -1,4 +1,4 @@ |
737 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
738 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
739 | # GNU Affero General Public License version 3 (see the file LICENSE). |
740 | |
741 | """Restful MAAS API. |
742 | @@ -43,7 +43,7 @@ |
743 | methods will take one special parameter, called `op`, to indicate what it is |
744 | you want to do. |
745 | |
746 | -For example, to list all nodes, you might GET "/api/2.0/nodes/?op=list". |
747 | +For example, to list all machines, you might GET "/api/2.0/machines". |
748 | """ |
749 | |
750 | __all__ = [ |
751 | |
752 | === modified file 'src/maasserver/api/domains.py' |
753 | --- src/maasserver/api/domains.py 2016-01-22 00:02:37 +0000 |
754 | +++ src/maasserver/api/domains.py 2016-03-02 18:21:51 +0000 |
755 | @@ -1,4 +1,4 @@ |
756 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
757 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
758 | # GNU Affero General Public License version 3 (see the file LICENSE). |
759 | |
760 | """API handlers: `Domain`.""" |
761 | |
762 | === modified file 'src/maasserver/api/events.py' |
763 | --- src/maasserver/api/events.py 2016-01-28 21:47:31 +0000 |
764 | +++ src/maasserver/api/events.py 2016-03-02 18:21:51 +0000 |
765 | @@ -1,4 +1,4 @@ |
766 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
767 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
768 | # GNU Affero General Public License version 3 (see the file LICENSE). |
769 | |
770 | __all__ = [ |
771 | |
772 | === modified file 'src/maasserver/api/fabrics.py' |
773 | --- src/maasserver/api/fabrics.py 2015-12-01 18:12:59 +0000 |
774 | +++ src/maasserver/api/fabrics.py 2016-03-02 18:21:51 +0000 |
775 | @@ -1,4 +1,4 @@ |
776 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
777 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
778 | # GNU Affero General Public License version 3 (see the file LICENSE). |
779 | |
780 | """API handlers: `Fabric`.""" |
781 | |
782 | === modified file 'src/maasserver/api/fannetworks.py' |
783 | --- src/maasserver/api/fannetworks.py 2015-12-01 18:12:59 +0000 |
784 | +++ src/maasserver/api/fannetworks.py 2016-03-02 18:21:51 +0000 |
785 | @@ -1,4 +1,4 @@ |
786 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
787 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
788 | # GNU Affero General Public License version 3 (see the file LICENSE). |
789 | |
790 | """API handlers: `Fan Network`.""" |
791 | |
792 | === modified file 'src/maasserver/api/files.py' |
793 | --- src/maasserver/api/files.py 2016-02-18 01:32:50 +0000 |
794 | +++ src/maasserver/api/files.py 2016-03-02 18:21:51 +0000 |
795 | @@ -1,4 +1,4 @@ |
796 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
797 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
798 | # GNU Affero General Public License version 3 (see the file LICENSE). |
799 | |
800 | """API handlers: `File`.""" |
801 | |
802 | === modified file 'src/maasserver/api/interfaces.py' |
803 | --- src/maasserver/api/interfaces.py 2015-12-16 00:01:56 +0000 |
804 | +++ src/maasserver/api/interfaces.py 2016-03-02 18:21:51 +0000 |
805 | @@ -1,4 +1,4 @@ |
806 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
807 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
808 | # GNU Affero General Public License version 3 (see the file LICENSE). |
809 | |
810 | """API handlers: `Interface`.""" |
811 | @@ -72,7 +72,7 @@ |
812 | |
813 | |
814 | class InterfacesHandler(OperationsHandler): |
815 | - """Manage interfaces on a node or device.""" |
816 | + """Manage interfaces on a node.""" |
817 | api_doc_section_name = "Interfaces" |
818 | create = update = delete = None |
819 | fields = DISPLAYED_INTERFACE_FIELDS |
820 | |
821 | === modified file 'src/maasserver/api/ip_addresses.py' |
822 | --- src/maasserver/api/ip_addresses.py 2016-02-17 18:21:13 +0000 |
823 | +++ src/maasserver/api/ip_addresses.py 2016-03-02 18:21:51 +0000 |
824 | @@ -1,4 +1,4 @@ |
825 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
826 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
827 | # GNU Affero General Public License version 3 (see the file LICENSE). |
828 | |
829 | """API handler: `StaticIPAddress`.""" |
830 | |
831 | === modified file 'src/maasserver/api/license_keys.py' |
832 | --- src/maasserver/api/license_keys.py 2015-12-01 18:12:59 +0000 |
833 | +++ src/maasserver/api/license_keys.py 2016-03-02 18:21:51 +0000 |
834 | @@ -1,4 +1,4 @@ |
835 | -# Copyright 2014 Canonical Ltd. This software is licensed under the |
836 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
837 | # GNU Affero General Public License version 3 (see the file LICENSE). |
838 | |
839 | """API handlers: `LicenseKey`.""" |
840 | |
841 | === modified file 'src/maasserver/api/logger.py' |
842 | --- src/maasserver/api/logger.py 2015-12-01 18:12:59 +0000 |
843 | +++ src/maasserver/api/logger.py 2016-03-02 18:21:51 +0000 |
844 | @@ -1,4 +1,4 @@ |
845 | -# Copyright 2014 Canonical Ltd. This software is licensed under the |
846 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
847 | # GNU Affero General Public License version 3 (see the file LICENSE). |
848 | |
849 | """API logger.""" |
850 | |
851 | === modified file 'src/maasserver/api/maas.py' |
852 | --- src/maasserver/api/maas.py 2016-02-11 15:06:36 +0000 |
853 | +++ src/maasserver/api/maas.py 2016-03-02 18:21:51 +0000 |
854 | @@ -1,4 +1,4 @@ |
855 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
856 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
857 | # GNU Affero General Public License version 3 (see the file LICENSE). |
858 | |
859 | """API handler: MAAS.""" |
860 | |
861 | === modified file 'src/maasserver/api/machines.py' |
862 | --- src/maasserver/api/machines.py 2016-03-02 02:22:45 +0000 |
863 | +++ src/maasserver/api/machines.py 2016-03-02 18:21:51 +0000 |
864 | @@ -1,4 +1,4 @@ |
865 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
866 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
867 | # GNU Affero General Public License version 3 (see the file LICENSE). |
868 | |
869 | __all__ = [ |
870 | @@ -59,12 +59,13 @@ |
871 | Unauthorized, |
872 | ) |
873 | from maasserver.forms import ( |
874 | - get_node_create_form, |
875 | - get_node_edit_form, |
876 | + get_machine_create_form, |
877 | + get_machine_edit_form, |
878 | ) |
879 | from maasserver.forms_commission import CommissionForm |
880 | from maasserver.models import ( |
881 | Config, |
882 | + Domain, |
883 | Machine, |
884 | RackController, |
885 | ) |
886 | @@ -91,6 +92,8 @@ |
887 | DISPLAYED_MACHINE_FIELDS = ( |
888 | 'system_id', |
889 | 'hostname', |
890 | + 'domain', |
891 | + 'fqdn', |
892 | 'owner', |
893 | 'macaddress_set', |
894 | 'boot_interface', |
895 | @@ -191,17 +194,25 @@ |
896 | |
897 | :param hostname: The new hostname for this machine. |
898 | :type hostname: unicode |
899 | + |
900 | + :param domain: The domain for this machine. If not given the default |
901 | + domain is used. |
902 | + :type domain: unicode |
903 | + |
904 | :param architecture: The new architecture for this machine. |
905 | :type architecture: unicode |
906 | + |
907 | :param min_hwe_kernel: A string containing the minimum kernel version |
908 | allowed to be ran on this machine. |
909 | :type min_hwe_kernel: unicode |
910 | + |
911 | :param power_type: The new power type for this machine. If you use the |
912 | default value, power_parameters will be set to the empty string. |
913 | Available to admin users. |
914 | See the `Power types`_ section for a list of the available power |
915 | types. |
916 | :type power_type: unicode |
917 | + |
918 | :param power_parameters_{param1}: The new value for the 'param1' |
919 | power parameter. Note that this is dynamic as the available |
920 | parameters depend on the selected value of the Machine's |
921 | @@ -209,19 +220,32 @@ |
922 | section for a list of the available power parameters for each |
923 | power type. |
924 | :type power_parameters_{param1}: unicode |
925 | + |
926 | :param power_parameters_skip_check: Whether or not the new power |
927 | parameters for this machine should be checked against the expected |
928 | power parameters for the machine's power type ('true' or 'false'). |
929 | The default is 'false'. |
930 | :type power_parameters_skip_check: unicode |
931 | + |
932 | :param zone: Name of a valid physical zone in which to place this |
933 | - machine |
934 | + machine. |
935 | :type zone: unicode |
936 | + |
937 | :param swap_size: Specifies the size of the swap file, in bytes. Field |
938 | accept K, M, G and T suffixes for values expressed respectively in |
939 | kilobytes, megabytes, gigabytes and terabytes. |
940 | :type swap_size: unicode |
941 | |
942 | + :param disable_ipv4: Whether or not IPv4 should be enabled on the |
943 | + machine. |
944 | + :type disable_ipv4: boolean |
945 | + |
946 | + :param cpu_count: The amount of CPU cores the machine has. |
947 | + :type cpu_count: integer |
948 | + |
949 | + :param memory: How much memory the machine has. |
950 | + :type memory: unicode |
951 | + |
952 | Returns 404 if the machine is not found. |
953 | Returns 403 if the user does not have permission to update the machine. |
954 | """ |
955 | @@ -234,7 +258,7 @@ |
956 | if 'power_type' not in request.data: |
957 | altered_query_data['power_type'] = machine.power_type |
958 | |
959 | - Form = get_node_edit_form(request.user) |
960 | + Form = get_machine_edit_form(request.user) |
961 | form = Form(data=altered_query_data, instance=machine) |
962 | |
963 | if form.is_valid(): |
964 | @@ -325,7 +349,7 @@ |
965 | if not machine.distro_series and not series: |
966 | series = Config.objects.get_config('default_distro_series') |
967 | if None in (series, license_key, hwe_kernel): |
968 | - Form = get_node_edit_form(request.user) |
969 | + Form = get_machine_edit_form(request.user) |
970 | form = Form(instance=machine) |
971 | if series is not None: |
972 | form.set_distro_series(series=series) |
973 | @@ -375,7 +399,7 @@ |
974 | machine.start(request.user, user_data=user_data, comment=comment) |
975 | except StaticIPAddressExhaustion: |
976 | # The API response should contain error text with the |
977 | - # system_id in it, as that is the primary API key to a node. |
978 | + # system_id in it, as that is the primary API key to a machine. |
979 | raise StaticIPAddressExhaustion( |
980 | "%s: Unable to allocate static IP due to address" |
981 | " exhaustion." % system_id) |
982 | @@ -383,7 +407,7 @@ |
983 | |
984 | @operation(idempotent=False) |
985 | def release(self, request, system_id): |
986 | - """Release a node. Opposite of `Machines.allocate`. |
987 | + """Release a machine. Opposite of `Machines.allocate`. |
988 | |
989 | :param comment: Optional comment for the event log. |
990 | :type comment: unicode |
991 | @@ -396,7 +420,7 @@ |
992 | machine = self.model.objects.get_node_or_404( |
993 | system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT) |
994 | if machine.status in (NODE_STATUS.RELEASING, NODE_STATUS.READY): |
995 | - # Nothing to do if this node is already releasing, otherwise |
996 | + # Nothing to do if this machine is already releasing, otherwise |
997 | # this may be a redundant retry, and the |
998 | # postcondition is achieved, so call this success. |
999 | pass |
1000 | @@ -536,7 +560,7 @@ |
1001 | |
1002 | If the default gateways need to be specific for this machine you can |
1003 | set which interface and subnet's gateway to use when this machine is |
1004 | - deployed with the `node-interfaces set-default-gateway` API. |
1005 | + deployed with the `interfaces set-default-gateway` API. |
1006 | |
1007 | Returns 404 if the machine could not be found. |
1008 | Returns 403 if the user does not have permission to clear the default |
1009 | @@ -579,17 +603,17 @@ |
1010 | user is not one. |
1011 | |
1012 | This returns the power parameters, if any, configured for a |
1013 | - node. For some types of power control this will include private |
1014 | + machine. For some types of power control this will include private |
1015 | information such as passwords and secret keys. |
1016 | |
1017 | - Returns 404 if the node is not found. |
1018 | + Returns 404 if the machine is not found. |
1019 | """ |
1020 | machine = get_object_or_404(self.model, system_id=system_id) |
1021 | return machine.power_parameters |
1022 | |
1023 | @operation(idempotent=True) |
1024 | def query_power_state(self, request, system_id): |
1025 | - """Query the power state of a node. |
1026 | + """Query the power state of a machine. |
1027 | |
1028 | Send a request to the machine's power controller which asks it about |
1029 | the machine's state. The reply to this could be delayed by up to |
1030 | @@ -663,7 +687,7 @@ |
1031 | |
1032 | The machine will be in the New state. |
1033 | |
1034 | - :param request: The http request for this node to be created. |
1035 | + :param request: The http request for this machine to be created. |
1036 | :return: A `Machine`. |
1037 | :rtype: :class:`maasserver.models.Machine`. |
1038 | :raises: ValidationError |
1039 | @@ -710,7 +734,7 @@ |
1040 | raise MAASAPIValidationError( |
1041 | 'min_hwe_kernel must be in the form of hwe-<LETTER>.') |
1042 | |
1043 | - Form = get_node_create_form(request.user) |
1044 | + Form = get_machine_create_form(request.user) |
1045 | form = Form(data=altered_query_data, request=request) |
1046 | if form.is_valid(): |
1047 | machine = form.save() |
1048 | @@ -745,18 +769,33 @@ |
1049 | :param architecture: A string containing the architecture type of |
1050 | the machine. (For example, "i386", or "amd64".) To determine the |
1051 | supported architectures, use the boot-resources endpoint. |
1052 | + :type architecture: unicode |
1053 | + |
1054 | :param min_hwe_kernel: A string containing the minimum kernel version |
1055 | allowed to be ran on this machine. |
1056 | + :type min_hwe_kernel: unicode |
1057 | + |
1058 | :param subarchitecture: A string containing the subarchitecture type |
1059 | of the machine. (For example, "generic" or "hwe-t".) To determine |
1060 | the supported subarchitectures, use the boot-resources endpoint. |
1061 | + :type subarchitecture: unicode |
1062 | + |
1063 | :param mac_addresses: One or more MAC addresses for the machine. To |
1064 | specify more than one MAC address, the parameter must be specified |
1065 | twice. (such as "machines new mac_addresses=01:02:03:04:05:06 |
1066 | mac_addresses=02:03:04:05:06:07") |
1067 | + :type mac_addresses: unicode |
1068 | + |
1069 | :param hostname: A hostname. If not given, one will be generated. |
1070 | + :type hostname: unicode |
1071 | + |
1072 | + :param domain: The domain of the machine. If not given the default |
1073 | + domain is used. |
1074 | + :type domain: unicode |
1075 | + |
1076 | :param power_type: A power management type, if applicable (e.g. |
1077 | "virsh", "ipmi"). |
1078 | + :type power_type:unicode |
1079 | """ |
1080 | return create_machine(request) |
1081 | |
1082 | @@ -774,7 +813,7 @@ |
1083 | |
1084 | |
1085 | class MachinesHandler(NodesHandler): |
1086 | - """Manage the collection of all the nodes in the MAAS.""" |
1087 | + """Manage the collection of all the machines in the MAAS.""" |
1088 | api_doc_section_name = "Machines" |
1089 | anonymous = AnonMachinesHandler |
1090 | base_model = Machine |
1091 | @@ -798,18 +837,33 @@ |
1092 | :param architecture: A string containing the architecture type of |
1093 | the machine. (For example, "i386", or "amd64".) To determine the |
1094 | supported architectures, use the boot-resources endpoint. |
1095 | + :type architecture: unicode |
1096 | + |
1097 | :param min_hwe_kernel: A string containing the minimum kernel version |
1098 | allowed to be ran on this machine. |
1099 | + :type min_hwe_kernel: unicode |
1100 | + |
1101 | :param subarchitecture: A string containing the subarchitecture type |
1102 | of the machine. (For example, "generic" or "hwe-t".) To determine |
1103 | the supported subarchitectures, use the boot-resources endpoint. |
1104 | + :type subarchitecture: unicode |
1105 | + |
1106 | :param mac_addresses: One or more MAC addresses for the machine. To |
1107 | specify more than one MAC address, the parameter must be specified |
1108 | twice. (such as "machines new mac_addresses=01:02:03:04:05:06 |
1109 | mac_addresses=02:03:04:05:06:07") |
1110 | + :type mac_addresses: unicode |
1111 | + |
1112 | :param hostname: A hostname. If not given, one will be generated. |
1113 | + :type hostname: unicode |
1114 | + |
1115 | + :param domain: The domain of the machine. If not given the default |
1116 | + domain is used. |
1117 | + :type domain: unicode |
1118 | + |
1119 | :param power_type: A power management type, if applicable (e.g. |
1120 | "virsh", "ipmi"). |
1121 | + :type power_type: unicode |
1122 | """ |
1123 | machine = create_machine(request) |
1124 | if request.user.is_superuser: |
1125 | @@ -857,7 +911,7 @@ |
1126 | Returns 403 if the user is not an admin. |
1127 | """ |
1128 | system_ids = set(request.POST.getlist('machines')) |
1129 | - # Check the existence of these nodes first. |
1130 | + # Check the existence of these machines first. |
1131 | self._check_system_ids_exist(system_ids) |
1132 | # Make sure that the user has the required permission. |
1133 | machines = self.base_model.objects.get_nodes( |
1134 | @@ -1045,7 +1099,7 @@ |
1135 | if machine is None: |
1136 | constraints = form.describe_constraints() |
1137 | if constraints == '': |
1138 | - # No constraints. That means no nodes at all were |
1139 | + # No constraints. That means no machines at all were |
1140 | # available. |
1141 | message = "No machine available." |
1142 | else: |
1143 | @@ -1140,7 +1194,7 @@ |
1144 | chassis types. |
1145 | :type password: unicode |
1146 | |
1147 | - :param accept_all: If true, all enlisted nodes will be |
1148 | + :param accept_all: If true, all enlisted machines will be |
1149 | commissioned. |
1150 | :type accept_all: unicode |
1151 | |
1152 | @@ -1149,6 +1203,9 @@ |
1153 | automatically determine the rack controller to use. |
1154 | :type rack_controller: unicode |
1155 | |
1156 | + :param domain: The domain that each new machine added should use. |
1157 | + :type domain: unicode |
1158 | + |
1159 | The following are optional if you are adding a virsh, vmware, or |
1160 | powerkvm chassis: |
1161 | |
1162 | @@ -1242,6 +1299,19 @@ |
1163 | chassis_type, content_type=( |
1164 | "text/plain; charset=%s" % settings.DEFAULT_CHARSET)) |
1165 | |
1166 | + # If given a domain make sure it exists first |
1167 | + domain_name = get_optional_param(request.POST, 'domain') |
1168 | + if domain_name is not None: |
1169 | + try: |
1170 | + domain = Domain.objects.get(id=int(domain_name)) |
1171 | + except ValueError: |
1172 | + try: |
1173 | + domain = Domain.objects.get(name=domain_name) |
1174 | + except Domain.DoesNotExist: |
1175 | + return HttpResponseNotFound( |
1176 | + "Unable to find specified domain %s" % domain_name) |
1177 | + domain_name = domain.name |
1178 | + |
1179 | rack_controller = get_optional_param(request.POST, 'rack_controller') |
1180 | if rack_controller is None: |
1181 | rack = RackController.objects.get_accessible_by_url(hostname) |
1182 | @@ -1262,7 +1332,8 @@ |
1183 | |
1184 | rack.add_chassis( |
1185 | request.user.username, chassis_type, hostname, username, password, |
1186 | - accept_all, prefix_filter, power_control, port, protocol) |
1187 | + accept_all, domain_name, prefix_filter, power_control, port, |
1188 | + protocol) |
1189 | |
1190 | return HttpResponse( |
1191 | "Asking %s to add machines from chassis %s" % ( |
1192 | |
1193 | === modified file 'src/maasserver/api/networks.py' |
1194 | --- src/maasserver/api/networks.py 2016-02-17 16:12:30 +0000 |
1195 | +++ src/maasserver/api/networks.py 2016-03-02 18:21:51 +0000 |
1196 | @@ -1,4 +1,4 @@ |
1197 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
1198 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
1199 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1200 | |
1201 | """API handlers: `Network`.""" |
1202 | |
1203 | === modified file 'src/maasserver/api/nodes.py' |
1204 | --- src/maasserver/api/nodes.py 2016-02-22 18:49:58 +0000 |
1205 | +++ src/maasserver/api/nodes.py 2016-03-02 18:21:51 +0000 |
1206 | @@ -136,15 +136,10 @@ |
1207 | """ |
1208 | api_doc_section_name = "Node" |
1209 | |
1210 | - create = None # Disable create. |
1211 | + # Disable create and update |
1212 | + create = update = None |
1213 | model = Node |
1214 | |
1215 | - # Override the 'hostname' field so that it returns the FQDN instead as |
1216 | - # this is used by Juju to reach that node. |
1217 | - @classmethod |
1218 | - def hostname(handler, node): |
1219 | - return node.fqdn |
1220 | - |
1221 | # Override 'owner' so it emits the owner's name rather than a |
1222 | # full nested user object. |
1223 | @classmethod |
1224 | @@ -315,7 +310,37 @@ |
1225 | base_model = Node |
1226 | |
1227 | def read(self, request): |
1228 | - """List all nodes.""" |
1229 | + """List Nodes visible to the user, optionally filtered by criteria. |
1230 | + |
1231 | + Nodes are sorted by id (i.e. most recent last) and grouped by type. |
1232 | + |
1233 | + :param hostname: An optional hostname. Only nodes relating to the node |
1234 | + with the matching hostname will be returned. This can be specified |
1235 | + multiple times to see multiple nodes. |
1236 | + :type hostname: unicode |
1237 | + |
1238 | + :param mac_address: An optional MAC address. Only nodes relating to the |
1239 | + node owning the specified MAC address will be returned. This can be |
1240 | + specified multiple times to see multiple nodes. |
1241 | + :type mac_address: unicode |
1242 | + |
1243 | + :param id: An optional list of system ids. Only nodes relating to the |
1244 | + nodes with matching system ids will be returned. |
1245 | + :type id: unicode |
1246 | + |
1247 | + :param domain: An optional name for a dns domain. Only nodes relating |
1248 | + to the nodes in the domain will be returned. |
1249 | + :type domain: unicode |
1250 | + |
1251 | + :param zone: An optional name for a physical zone. Only nodes relating |
1252 | + to the nodes in the zone will be returned. |
1253 | + :type zone: unicode |
1254 | + |
1255 | + :param agent_name: An optional agent name. Only nodes relating to the |
1256 | + nodes with matching agent names will be returned. |
1257 | + :type agent_name: unicode |
1258 | + """ |
1259 | + |
1260 | if self.base_model == Node: |
1261 | # Avoid circular dependencies |
1262 | from maasserver.api.devices import DevicesHandler |
1263 | |
1264 | === modified file 'src/maasserver/api/not_found.py' |
1265 | --- src/maasserver/api/not_found.py 2015-12-01 18:12:59 +0000 |
1266 | +++ src/maasserver/api/not_found.py 2016-03-02 18:21:51 +0000 |
1267 | @@ -1,4 +1,4 @@ |
1268 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
1269 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
1270 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1271 | |
1272 | """Not found API handler.""" |
1273 | |
1274 | === modified file 'src/maasserver/api/pxeconfig.py' |
1275 | --- src/maasserver/api/pxeconfig.py 2016-02-11 15:06:36 +0000 |
1276 | +++ src/maasserver/api/pxeconfig.py 2016-03-02 18:21:51 +0000 |
1277 | @@ -1,4 +1,4 @@ |
1278 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
1279 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
1280 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1281 | |
1282 | """API handler: `pxeconfig`.""" |
1283 | |
1284 | === modified file 'src/maasserver/api/rackcontrollers.py' |
1285 | --- src/maasserver/api/rackcontrollers.py 2016-02-19 08:38:36 +0000 |
1286 | +++ src/maasserver/api/rackcontrollers.py 2016-03-02 18:21:51 +0000 |
1287 | @@ -1,4 +1,4 @@ |
1288 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
1289 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
1290 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1291 | |
1292 | __all__ = [ |
1293 | @@ -8,9 +8,9 @@ |
1294 | |
1295 | from django.conf import settings |
1296 | from django.http import HttpResponse |
1297 | -from maasserver.api.machines import ( |
1298 | - MachineHandler, |
1299 | - MachinesHandler, |
1300 | +from maasserver.api.nodes import ( |
1301 | + NodeHandler, |
1302 | + NodesHandler, |
1303 | ) |
1304 | from maasserver.api.support import ( |
1305 | admin_method, |
1306 | @@ -27,15 +27,14 @@ |
1307 | DISPLAYED_RACK_CONTROLLER_FIELDS = ( |
1308 | 'system_id', |
1309 | 'hostname', |
1310 | + 'domain', |
1311 | + 'fqdn', |
1312 | 'architecture', |
1313 | 'cpu_count', |
1314 | 'memory', |
1315 | 'swap_size', |
1316 | - 'status', |
1317 | 'osystem', |
1318 | 'distro_series', |
1319 | - 'power_type', |
1320 | - 'power_state', |
1321 | 'ip_addresses', |
1322 | ('interface_set', ( |
1323 | 'id', |
1324 | @@ -54,14 +53,12 @@ |
1325 | )), |
1326 | 'zone', |
1327 | 'status_action', |
1328 | - 'status_message', |
1329 | - 'status_name', |
1330 | 'node_type', |
1331 | 'node_type_name', |
1332 | ) |
1333 | |
1334 | |
1335 | -class RackControllerHandler(MachineHandler): |
1336 | +class RackControllerHandler(NodeHandler): |
1337 | """Manage an individual rack controller. |
1338 | |
1339 | The rack controller is identified by its system_id. |
1340 | @@ -75,7 +72,7 @@ |
1341 | def refresh(self, request, system_id): |
1342 | """Refresh the hardware information for a specific rack controller. |
1343 | |
1344 | - Returns 404 if the node is not found. |
1345 | + Returns 404 if the rack-controller is not found. |
1346 | Returns 403 if the user does not have permission to refresh the rack. |
1347 | """ |
1348 | rack = self.model.objects.get_node_or_404( |
1349 | @@ -110,7 +107,7 @@ |
1350 | return ('rackcontroller_handler', (rackcontroller_id, )) |
1351 | |
1352 | |
1353 | -class RackControllersHandler(MachinesHandler): |
1354 | +class RackControllersHandler(NodesHandler): |
1355 | """Manage the collection of all rack controllers in MAAS.""" |
1356 | api_doc_section_name = "RackControllers" |
1357 | base_model = RackController |
1358 | |
1359 | === modified file 'src/maasserver/api/raid.py' |
1360 | --- src/maasserver/api/raid.py 2015-12-16 00:01:56 +0000 |
1361 | +++ src/maasserver/api/raid.py 2016-03-02 18:21:51 +0000 |
1362 | @@ -1,4 +1,4 @@ |
1363 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
1364 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
1365 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1366 | |
1367 | """API handlers: `RAID`.""" |
1368 | @@ -39,7 +39,7 @@ |
1369 | |
1370 | |
1371 | class RaidsHandler(OperationsHandler): |
1372 | - """Manage all RAID devices on a node.""" |
1373 | + """Manage all RAID devices on a machine.""" |
1374 | api_doc_section_name = "RAID Devices" |
1375 | update = delete = None |
1376 | fields = DISPLAYED_RAID_FIELDS |
1377 | @@ -67,7 +67,7 @@ |
1378 | system_id, request.user, NODE_PERMISSION.ADMIN) |
1379 | if machine.status != NODE_STATUS.READY: |
1380 | raise NodeStateViolation( |
1381 | - "Cannot create RAID because the node is not Ready.") |
1382 | + "Cannot create RAID because the machine is not Ready.") |
1383 | form = CreateRaidForm(machine, data=request.data) |
1384 | if form.is_valid(): |
1385 | return form.save() |
1386 | @@ -75,7 +75,7 @@ |
1387 | raise MAASAPIValidationError(form.errors) |
1388 | |
1389 | def read(self, request, system_id): |
1390 | - """List all RAID devices belonging to node. |
1391 | + """List all RAID devices belonging to a machine. |
1392 | |
1393 | Returns 404 if the machine is not found. |
1394 | """ |
1395 | @@ -85,7 +85,7 @@ |
1396 | |
1397 | |
1398 | class RaidHandler(OperationsHandler): |
1399 | - """Manage a specific RAID device on a node.""" |
1400 | + """Manage a specific RAID device on a machine.""" |
1401 | api_doc_section_name = "RAID Device" |
1402 | create = None |
1403 | model = RAID |
1404 | @@ -142,15 +142,15 @@ |
1405 | ] |
1406 | |
1407 | def read(self, request, system_id, raid_id): |
1408 | - """Read RAID device on node. |
1409 | + """Read RAID device on a machine. |
1410 | |
1411 | - Returns 404 if the node or RAID is not found. |
1412 | + Returns 404 if the machine or RAID is not found. |
1413 | """ |
1414 | return RAID.objects.get_object_or_404( |
1415 | system_id, raid_id, request.user, NODE_PERMISSION.VIEW) |
1416 | |
1417 | def update(self, request, system_id, raid_id): |
1418 | - """Update RAID on node. |
1419 | + """Update RAID on a machine. |
1420 | |
1421 | :param name: Name of the RAID. |
1422 | :param uuid: UUID of the RAID. |
1423 | @@ -165,15 +165,15 @@ |
1424 | :param remove_spare_partitions: Spare partitions to remove from the |
1425 | RAID. |
1426 | |
1427 | - Returns 404 if the node or RAID is not found. |
1428 | - Returns 409 if the node is not Ready. |
1429 | + Returns 404 if the machine or RAID is not found. |
1430 | + Returns 409 if the machine is not Ready. |
1431 | """ |
1432 | raid = RAID.objects.get_object_or_404( |
1433 | system_id, raid_id, request.user, NODE_PERMISSION.ADMIN) |
1434 | node = raid.get_node() |
1435 | if node.status != NODE_STATUS.READY: |
1436 | raise NodeStateViolation( |
1437 | - "Cannot update RAID because the node is not Ready.") |
1438 | + "Cannot update RAID because the machine is not Ready.") |
1439 | form = UpdateRaidForm(raid, data=request.data) |
1440 | if form.is_valid(): |
1441 | return form.save() |
1442 | @@ -181,16 +181,16 @@ |
1443 | raise MAASAPIValidationError(form.errors) |
1444 | |
1445 | def delete(self, request, system_id, raid_id): |
1446 | - """Delete RAID on node. |
1447 | + """Delete RAID on a machine. |
1448 | |
1449 | - Returns 404 if the node or RAID is not found. |
1450 | - Returns 409 if the node is not Ready. |
1451 | + Returns 404 if the machine or RAID is not found. |
1452 | + Returns 409 if the machine is not Ready. |
1453 | """ |
1454 | raid = RAID.objects.get_object_or_404( |
1455 | system_id, raid_id, request.user, NODE_PERMISSION.ADMIN) |
1456 | node = raid.get_node() |
1457 | if node.status != NODE_STATUS.READY: |
1458 | raise NodeStateViolation( |
1459 | - "Cannot delete RAID because the node is not Ready.") |
1460 | + "Cannot delete RAID because the machine is not Ready.") |
1461 | raid.delete() |
1462 | return rc.DELETED |
1463 | |
1464 | === modified file 'src/maasserver/api/spaces.py' |
1465 | --- src/maasserver/api/spaces.py 2015-12-01 18:12:59 +0000 |
1466 | +++ src/maasserver/api/spaces.py 2016-03-02 18:21:51 +0000 |
1467 | @@ -1,4 +1,4 @@ |
1468 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
1469 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
1470 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1471 | |
1472 | """API handlers: `Space`.""" |
1473 | |
1474 | === modified file 'src/maasserver/api/ssh_keys.py' |
1475 | --- src/maasserver/api/ssh_keys.py 2016-02-19 08:38:36 +0000 |
1476 | +++ src/maasserver/api/ssh_keys.py 2016-03-02 18:21:51 +0000 |
1477 | @@ -1,4 +1,4 @@ |
1478 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
1479 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
1480 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1481 | |
1482 | """API handlers: `SSHKey`.""" |
1483 | |
1484 | === modified file 'src/maasserver/api/ssl_keys.py' |
1485 | --- src/maasserver/api/ssl_keys.py 2016-02-19 08:38:36 +0000 |
1486 | +++ src/maasserver/api/ssl_keys.py 2016-03-02 18:21:51 +0000 |
1487 | @@ -1,4 +1,4 @@ |
1488 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
1489 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
1490 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1491 | |
1492 | """API handlers: `SSLKey`.""" |
1493 | |
1494 | === modified file 'src/maasserver/api/subnets.py' |
1495 | --- src/maasserver/api/subnets.py 2016-01-29 17:32:25 +0000 |
1496 | +++ src/maasserver/api/subnets.py 2016-03-02 18:21:51 +0000 |
1497 | @@ -1,4 +1,4 @@ |
1498 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
1499 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
1500 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1501 | |
1502 | """API handlers: `Subnet`.""" |
1503 | |
1504 | === modified file 'src/maasserver/api/support.py' |
1505 | --- src/maasserver/api/support.py 2016-02-25 08:52:39 +0000 |
1506 | +++ src/maasserver/api/support.py 2016-03-02 18:21:51 +0000 |
1507 | @@ -1,4 +1,4 @@ |
1508 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
1509 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
1510 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1511 | |
1512 | """Supporting infrastructure for Piston-based APIs in MAAS.""" |
1513 | |
1514 | === modified file 'src/maasserver/api/tags.py' |
1515 | --- src/maasserver/api/tags.py 2016-02-19 08:38:36 +0000 |
1516 | +++ src/maasserver/api/tags.py 2016-03-02 18:21:51 +0000 |
1517 | @@ -1,4 +1,4 @@ |
1518 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
1519 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
1520 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1521 | |
1522 | """API handlers: `Tag`.""" |
1523 | |
1524 | === modified file 'src/maasserver/api/tests/test_devices.py' |
1525 | --- src/maasserver/api/tests/test_devices.py 2016-02-26 18:39:26 +0000 |
1526 | +++ src/maasserver/api/tests/test_devices.py 2016-03-02 18:21:51 +0000 |
1527 | @@ -13,7 +13,10 @@ |
1528 | NODE_STATUS, |
1529 | NODE_TYPE, |
1530 | ) |
1531 | -from maasserver.models import Device |
1532 | +from maasserver.models import ( |
1533 | + Device, |
1534 | + Domain, |
1535 | +) |
1536 | from maasserver.testing.api import APITestCase |
1537 | from maasserver.testing.factory import factory |
1538 | from maasserver.utils.converters import json_load_bytes |
1539 | @@ -72,6 +75,48 @@ |
1540 | self.assertEquals(parent, device.parent) |
1541 | self.assertEqual(device.node_type, NODE_TYPE.DEVICE) |
1542 | |
1543 | + def test_POST_creates_device_with_default_domain(self): |
1544 | + hostname = factory.make_name('host') |
1545 | + macs = { |
1546 | + factory.make_mac_address() |
1547 | + for _ in range(random.randint(1, 2)) |
1548 | + } |
1549 | + response = self.client.post( |
1550 | + reverse('devices_handler'), |
1551 | + { |
1552 | + 'hostname': hostname, |
1553 | + 'mac_addresses': macs, |
1554 | + }) |
1555 | + self.assertEqual( |
1556 | + http.client.OK, response.status_code, response.content) |
1557 | + system_id = json_load_bytes(response.content)['system_id'] |
1558 | + device = Device.objects.get(system_id=system_id) |
1559 | + self.assertEquals(hostname, device.hostname) |
1560 | + self.assertEquals(Domain.objects.get_default_domain(), device.domain) |
1561 | + self.assertEqual(device.node_type, NODE_TYPE.DEVICE) |
1562 | + |
1563 | + def test_POST_creates_device_with_domain(self): |
1564 | + hostname = factory.make_name('host') |
1565 | + domain = factory.make_Domain() |
1566 | + macs = { |
1567 | + factory.make_mac_address() |
1568 | + for _ in range(random.randint(1, 2)) |
1569 | + } |
1570 | + response = self.client.post( |
1571 | + reverse('devices_handler'), |
1572 | + { |
1573 | + 'hostname': hostname, |
1574 | + 'mac_addresses': macs, |
1575 | + 'domain': domain.name, |
1576 | + }) |
1577 | + self.assertEqual( |
1578 | + http.client.OK, response.status_code, response.content) |
1579 | + system_id = json_load_bytes(response.content)['system_id'] |
1580 | + device = Device.objects.get(system_id=system_id) |
1581 | + self.assertEquals(hostname, device.hostname) |
1582 | + self.assertEquals(domain, device.domain) |
1583 | + self.assertEqual(device.node_type, NODE_TYPE.DEVICE) |
1584 | + |
1585 | def test_POST_returns_limited_fields(self): |
1586 | response = self.client.post( |
1587 | reverse('devices_handler'), |
1588 | @@ -83,6 +128,8 @@ |
1589 | self.assertItemsEqual( |
1590 | [ |
1591 | 'hostname', |
1592 | + 'domain', |
1593 | + 'fqdn', |
1594 | 'owner', |
1595 | 'system_id', |
1596 | 'macaddress_set', |
1597 | @@ -162,6 +209,8 @@ |
1598 | self.assertItemsEqual( |
1599 | [ |
1600 | 'hostname', |
1601 | + 'domain', |
1602 | + 'fqdn', |
1603 | 'owner', |
1604 | 'system_id', |
1605 | 'macaddress_set', |
1606 | @@ -195,7 +244,7 @@ |
1607 | |
1608 | response = self.client.post(get_device_uri(device)) |
1609 | self.assertEqual( |
1610 | - http.client.METHOD_NOT_ALLOWED, response.status_code, |
1611 | + http.client.BAD_REQUEST, response.status_code, |
1612 | response.content) |
1613 | |
1614 | def test_GET_reads_device(self): |
1615 | |
1616 | === modified file 'src/maasserver/api/tests/test_doc.py' |
1617 | --- src/maasserver/api/tests/test_doc.py 2016-02-02 14:20:45 +0000 |
1618 | +++ src/maasserver/api/tests/test_doc.py 2016-03-02 18:21:51 +0000 |
1619 | @@ -5,7 +5,10 @@ |
1620 | |
1621 | __all__ = [] |
1622 | |
1623 | +import http.client |
1624 | from inspect import getdoc |
1625 | +from io import StringIO |
1626 | +import sys |
1627 | import types |
1628 | |
1629 | from django.conf.urls import ( |
1630 | @@ -74,6 +77,14 @@ |
1631 | name = factory.make_name("module") |
1632 | return types.ModuleType(name) |
1633 | |
1634 | + def test_anon_api_doc(self): |
1635 | + # The documentation is accessible to anon users. |
1636 | + self.patch(sys, "stderr", StringIO()) |
1637 | + response = self.client.get(reverse('api-doc')) |
1638 | + self.assertEqual(http.client.OK, response.status_code) |
1639 | + # No error or warning are emitted by docutils. |
1640 | + self.assertEqual("", sys.stderr.getvalue()) |
1641 | + |
1642 | def test_urlpatterns_empty(self): |
1643 | # No resources are found in empty modules. |
1644 | module = self.make_module() |
1645 | |
1646 | === modified file 'src/maasserver/api/tests/test_enlistment.py' |
1647 | --- src/maasserver/api/tests/test_enlistment.py 2016-03-01 01:15:22 +0000 |
1648 | +++ src/maasserver/api/tests/test_enlistment.py 2016-03-02 18:21:51 +0000 |
1649 | @@ -64,7 +64,7 @@ |
1650 | self.assertIn('application/json', response['Content-Type']) |
1651 | domain_name = Domain.objects.get_default_domain().name |
1652 | self.assertEqual( |
1653 | - 'diane.%s' % domain_name, parsed_result['hostname']) |
1654 | + 'diane.%s' % domain_name, parsed_result['fqdn']) |
1655 | self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
1656 | [diane] = Machine.objects.filter(hostname='diane') |
1657 | self.assertEqual(architecture, diane.architecture) |
1658 | @@ -129,7 +129,7 @@ |
1659 | self.assertIn('application/json', response['Content-Type']) |
1660 | domain_name = Domain.objects.get_default_domain().name |
1661 | self.assertEqual( |
1662 | - 'diane.%s' % domain_name, parsed_result['hostname']) |
1663 | + 'diane.%s' % domain_name, parsed_result['fqdn']) |
1664 | self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
1665 | [diane] = Machine.objects.filter(hostname='diane') |
1666 | self.assertEqual(architecture, diane.architecture) |
1667 | @@ -152,7 +152,7 @@ |
1668 | self.assertIn('application/json', response['Content-Type']) |
1669 | domain_name = Domain.objects.get_default_domain().name |
1670 | self.assertEqual( |
1671 | - 'diane.%s' % domain_name, parsed_result['hostname']) |
1672 | + 'diane.%s' % domain_name, parsed_result['fqdn']) |
1673 | self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
1674 | [diane] = Machine.objects.filter(hostname='diane') |
1675 | self.assertEqual(architecture, diane.architecture) |
1676 | @@ -275,6 +275,30 @@ |
1677 | self.assertItemsEqual( |
1678 | ['architecture'], parsed_result, response.content) |
1679 | |
1680 | + def test_POST_create_creates_machine_with_domain(self): |
1681 | + domain = factory.make_Domain() |
1682 | + # The API allows a Machine to be created. |
1683 | + architecture = make_usable_architecture(self) |
1684 | + response = self.client.post( |
1685 | + reverse('machines_handler'), |
1686 | + { |
1687 | + 'hostname': 'diane', |
1688 | + 'architecture': architecture.split('/')[0], |
1689 | + 'subarchitecture': architecture.split('/')[1], |
1690 | + 'power_type': 'manual', |
1691 | + 'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'], |
1692 | + 'domain': domain.name, |
1693 | + }) |
1694 | + |
1695 | + self.assertEqual(http.client.OK, response.status_code) |
1696 | + parsed_result = json_load_bytes(response.content) |
1697 | + self.assertIn('application/json', response['Content-Type']) |
1698 | + self.assertEqual( |
1699 | + 'diane.%s' % domain.name, parsed_result['fqdn']) |
1700 | + self.assertNotEqual(0, len(parsed_result.get('system_id'))) |
1701 | + [diane] = Machine.objects.filter(hostname='diane') |
1702 | + self.assertEqual(architecture, diane.architecture) |
1703 | + |
1704 | |
1705 | class MachineHostnameEnlistmentTest( |
1706 | MultipleUsersScenarios, MAASServerTestCase): |
1707 | @@ -307,7 +331,7 @@ |
1708 | hostname_without_domain, |
1709 | Domain.objects.get_default_domain().name) |
1710 | self.assertEqual( |
1711 | - expected_hostname, parsed_result.get('hostname')) |
1712 | + expected_hostname, parsed_result.get('fqdn')) |
1713 | |
1714 | |
1715 | class NonAdminEnlistmentAPITest( |
1716 | @@ -376,6 +400,8 @@ |
1717 | self.assertItemsEqual( |
1718 | [ |
1719 | 'hostname', |
1720 | + 'domain', |
1721 | + 'fqdn', |
1722 | 'owner', |
1723 | 'system_id', |
1724 | 'architecture', |
1725 | @@ -482,6 +508,8 @@ |
1726 | self.assertItemsEqual( |
1727 | [ |
1728 | 'hostname', |
1729 | + 'domain', |
1730 | + 'fqdn', |
1731 | 'owner', |
1732 | 'system_id', |
1733 | 'macaddress_set', |
1734 | @@ -644,6 +672,8 @@ |
1735 | self.assertItemsEqual( |
1736 | [ |
1737 | 'hostname', |
1738 | + 'domain', |
1739 | + 'fqdn', |
1740 | 'owner', |
1741 | 'system_id', |
1742 | 'macaddress_set', |
1743 | |
1744 | === modified file 'src/maasserver/api/tests/test_machine.py' |
1745 | --- src/maasserver/api/tests/test_machine.py 2016-02-29 07:45:25 +0000 |
1746 | +++ src/maasserver/api/tests/test_machine.py 2016-03-02 18:21:51 +0000 |
1747 | @@ -7,10 +7,7 @@ |
1748 | |
1749 | from base64 import b64encode |
1750 | import http.client |
1751 | -from io import StringIO |
1752 | -import sys |
1753 | |
1754 | -import bson |
1755 | from django.conf import settings |
1756 | from django.core.urlresolvers import reverse |
1757 | from django.db import transaction |
1758 | @@ -20,7 +17,6 @@ |
1759 | INTERFACE_TYPE, |
1760 | IPADDRESS_TYPE, |
1761 | NODE_STATUS, |
1762 | - NODE_STATUS_CHOICES, |
1763 | NODE_STATUS_CHOICES_DICT, |
1764 | NODE_TYPE, |
1765 | NODE_TYPE_CHOICES, |
1766 | @@ -66,10 +62,6 @@ |
1767 | from metadataserver.nodeinituser import get_node_init_user |
1768 | from mock import ANY |
1769 | from netaddr import IPNetwork |
1770 | -from provisioningserver.refresh.node_info_scripts import ( |
1771 | - LLDP_OUTPUT_NAME, |
1772 | - LSHW_OUTPUT_NAME, |
1773 | -) |
1774 | from provisioningserver.rpc.exceptions import PowerActionAlreadyInProgress |
1775 | from provisioningserver.utils.enum import map_enum |
1776 | from twisted.internet import defer |
1777 | @@ -78,19 +70,6 @@ |
1778 | |
1779 | class MachineAnonAPITest(MAASServerTestCase): |
1780 | |
1781 | - def setUp(self): |
1782 | - super(MachineAnonAPITest, self).setUp() |
1783 | - self.patch(node_module.Node, '_start') |
1784 | - self.patch(node_module.Node, '_stop') |
1785 | - |
1786 | - def test_anon_api_doc(self): |
1787 | - # The documentation is accessible to anon users. |
1788 | - self.patch(sys, "stderr", StringIO()) |
1789 | - response = self.client.get(reverse('api-doc')) |
1790 | - self.assertEqual(http.client.OK, response.status_code) |
1791 | - # No error or warning are emitted by docutils. |
1792 | - self.assertEqual("", sys.stderr.getvalue()) |
1793 | - |
1794 | def test_machine_init_user_cannot_access(self): |
1795 | token = NodeKey.objects.get_token_for_node(factory.make_Node()) |
1796 | client = OAuthAuthenticatedClient(get_node_init_user(), token) |
1797 | @@ -147,7 +126,7 @@ |
1798 | domain_name = Domain.objects.get_default_domain().name |
1799 | self.assertEqual( |
1800 | "%s.%s" % (machine.hostname, domain_name), |
1801 | - parsed_result['hostname']) |
1802 | + parsed_result['fqdn']) |
1803 | self.assertEqual(machine.system_id, parsed_result['system_id']) |
1804 | |
1805 | def test_GET_returns_associated_tag(self): |
1806 | @@ -924,7 +903,7 @@ |
1807 | self.assertEqual(http.client.OK, response.status_code) |
1808 | domain_name = Domain.objects.get_default_domain().name |
1809 | self.assertEqual( |
1810 | - 'francis.%s' % domain_name, parsed_result['hostname']) |
1811 | + 'francis.%s' % domain_name, parsed_result['fqdn']) |
1812 | self.assertEqual(0, Machine.objects.filter(hostname='diane').count()) |
1813 | self.assertEqual(1, Machine.objects.filter(hostname='francis').count()) |
1814 | |
1815 | @@ -1480,188 +1459,6 @@ |
1816 | response.content) |
1817 | |
1818 | |
1819 | -class TestGetDetails(APITestCase): |
1820 | - """Tests for /api/2.0/machines/<machine>/?op=details.""" |
1821 | - |
1822 | - def make_lshw_result(self, machine, script_result=0): |
1823 | - return factory.make_NodeResult_for_commissioning( |
1824 | - node=machine, name=LSHW_OUTPUT_NAME, |
1825 | - script_result=script_result) |
1826 | - |
1827 | - def make_lldp_result(self, machine, script_result=0): |
1828 | - return factory.make_NodeResult_for_commissioning( |
1829 | - node=machine, name=LLDP_OUTPUT_NAME, script_result=script_result) |
1830 | - |
1831 | - def get_details(self, machine): |
1832 | - url = reverse('machine_handler', args=[machine.system_id]) |
1833 | - response = self.client.get(url, {'op': 'details'}) |
1834 | - self.assertEqual(http.client.OK, response.status_code) |
1835 | - self.assertEqual('application/bson', response['content-type']) |
1836 | - return bson.BSON(response.content).decode() |
1837 | - |
1838 | - def test_GET_returns_empty_details_when_there_are_none(self): |
1839 | - machine = factory.make_Node() |
1840 | - self.assertDictEqual( |
1841 | - {"lshw": None, "lldp": None}, |
1842 | - self.get_details(machine)) |
1843 | - |
1844 | - def test_GET_returns_all_details(self): |
1845 | - machine = factory.make_Node() |
1846 | - lshw_result = self.make_lshw_result(machine) |
1847 | - lldp_result = self.make_lldp_result(machine) |
1848 | - self.assertDictEqual( |
1849 | - {"lshw": lshw_result.data, |
1850 | - "lldp": lldp_result.data}, |
1851 | - self.get_details(machine)) |
1852 | - |
1853 | - def test_GET_returns_only_those_details_that_exist(self): |
1854 | - machine = factory.make_Node() |
1855 | - lshw_result = self.make_lshw_result(machine) |
1856 | - self.assertDictEqual( |
1857 | - {"lshw": lshw_result.data, |
1858 | - "lldp": None}, |
1859 | - self.get_details(machine)) |
1860 | - |
1861 | - def test_GET_returns_not_found_when_machine_does_not_exist(self): |
1862 | - url = reverse('machine_handler', args=['does-not-exist']) |
1863 | - response = self.client.get(url, {'op': 'details'}) |
1864 | - self.assertEqual(http.client.NOT_FOUND, response.status_code) |
1865 | - |
1866 | - |
1867 | -class TestMarkBroken(APITestCase): |
1868 | - """Tests for /api/2.0/machines/<machine>/?op=mark_broken""" |
1869 | - |
1870 | - def get_machine_uri(self, machine): |
1871 | - """Get the API URI for `machine`.""" |
1872 | - return reverse('machine_handler', args=[machine.system_id]) |
1873 | - |
1874 | - def test_mark_broken_changes_status(self): |
1875 | - machine = factory.make_Node( |
1876 | - status=NODE_STATUS.COMMISSIONING, owner=self.logged_in_user) |
1877 | - response = self.client.post( |
1878 | - self.get_machine_uri(machine), {'op': 'mark_broken'}) |
1879 | - self.assertEqual(http.client.OK, response.status_code) |
1880 | - self.assertEqual(NODE_STATUS.BROKEN, reload_object(machine).status) |
1881 | - |
1882 | - def test_mark_broken_updates_error_description(self): |
1883 | - # 'error_description' parameter was renamed 'comment' for consistency |
1884 | - # make sure this comment updates the machine's error_description |
1885 | - machine = factory.make_Node( |
1886 | - status=NODE_STATUS.COMMISSIONING, owner=self.logged_in_user) |
1887 | - comment = factory.make_name('comment') |
1888 | - response = self.client.post( |
1889 | - self.get_machine_uri(machine), |
1890 | - {'op': 'mark_broken', 'comment': comment}) |
1891 | - self.assertEqual(http.client.OK, response.status_code) |
1892 | - machine = reload_object(machine) |
1893 | - self.assertEqual( |
1894 | - (NODE_STATUS.BROKEN, comment), |
1895 | - (machine.status, machine.error_description) |
1896 | - ) |
1897 | - |
1898 | - def test_mark_broken_updates_error_description_compatibility(self): |
1899 | - # test old 'error_description' parameter is honored for compatibility |
1900 | - machine = factory.make_Node( |
1901 | - status=NODE_STATUS.COMMISSIONING, owner=self.logged_in_user) |
1902 | - error_description = factory.make_name('error_description') |
1903 | - response = self.client.post( |
1904 | - self.get_machine_uri(machine), |
1905 | - {'op': 'mark_broken', 'error_description': error_description}) |
1906 | - self.assertEqual(http.client.OK, response.status_code) |
1907 | - machine = reload_object(machine) |
1908 | - self.assertEqual( |
1909 | - (NODE_STATUS.BROKEN, error_description), |
1910 | - (machine.status, machine.error_description) |
1911 | - ) |
1912 | - |
1913 | - def test_mark_broken_passes_comment(self): |
1914 | - machine = factory.make_Node( |
1915 | - status=NODE_STATUS.COMMISSIONING, owner=self.logged_in_user) |
1916 | - machine_mark_broken = self.patch(node_module.Machine, 'mark_broken') |
1917 | - comment = factory.make_name('comment') |
1918 | - self.client.post( |
1919 | - self.get_machine_uri(machine), |
1920 | - {'op': 'mark_broken', 'comment': comment}) |
1921 | - self.assertThat( |
1922 | - machine_mark_broken, |
1923 | - MockCalledOnceWith(self.logged_in_user, comment)) |
1924 | - |
1925 | - def test_mark_broken_handles_missing_comment(self): |
1926 | - machine = factory.make_Node( |
1927 | - status=NODE_STATUS.COMMISSIONING, owner=self.logged_in_user) |
1928 | - machine_mark_broken = self.patch(node_module.Machine, 'mark_broken') |
1929 | - self.client.post( |
1930 | - self.get_machine_uri(machine), {'op': 'mark_broken'}) |
1931 | - self.assertThat( |
1932 | - machine_mark_broken, |
1933 | - MockCalledOnceWith(self.logged_in_user, None)) |
1934 | - |
1935 | - def test_mark_broken_requires_ownership(self): |
1936 | - machine = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
1937 | - response = self.client.post( |
1938 | - self.get_machine_uri(machine), {'op': 'mark_broken'}) |
1939 | - self.assertEqual(http.client.FORBIDDEN, response.status_code) |
1940 | - |
1941 | - def test_mark_broken_allowed_from_any_other_state(self): |
1942 | - self.patch(node_module.Node, "_stop") |
1943 | - for status, _ in NODE_STATUS_CHOICES: |
1944 | - if status == NODE_STATUS.BROKEN: |
1945 | - continue |
1946 | - |
1947 | - machine = factory.make_Node( |
1948 | - status=status, owner=self.logged_in_user) |
1949 | - response = self.client.post( |
1950 | - self.get_machine_uri(machine), {'op': 'mark_broken'}) |
1951 | - self.expectThat( |
1952 | - response.status_code, Equals(http.client.OK), response) |
1953 | - machine = reload_object(machine) |
1954 | - self.expectThat(machine.status, Equals(NODE_STATUS.BROKEN)) |
1955 | - |
1956 | - |
1957 | -class TestMarkFixed(APITestCase): |
1958 | - """Tests for /api/2.0/machines/<machine>/?op=mark_fixed""" |
1959 | - |
1960 | - def get_machine_uri(self, machine): |
1961 | - """Get the API URI for `machine`.""" |
1962 | - return reverse('machine_handler', args=[machine.system_id]) |
1963 | - |
1964 | - def test_mark_fixed_changes_status(self): |
1965 | - self.become_admin() |
1966 | - machine = factory.make_Node(status=NODE_STATUS.BROKEN) |
1967 | - response = self.client.post( |
1968 | - self.get_machine_uri(machine), {'op': 'mark_fixed'}) |
1969 | - self.assertEqual(http.client.OK, response.status_code) |
1970 | - self.assertEqual(NODE_STATUS.READY, reload_object(machine).status) |
1971 | - |
1972 | - def test_mark_fixed_requires_admin(self): |
1973 | - machine = factory.make_Node(status=NODE_STATUS.BROKEN) |
1974 | - response = self.client.post( |
1975 | - self.get_machine_uri(machine), {'op': 'mark_fixed'}) |
1976 | - self.assertEqual(http.client.FORBIDDEN, response.status_code) |
1977 | - |
1978 | - def test_mark_fixed_passes_comment(self): |
1979 | - self.become_admin() |
1980 | - machine = factory.make_Node(status=NODE_STATUS.BROKEN) |
1981 | - machine_mark_fixed = self.patch(node_module.Machine, 'mark_fixed') |
1982 | - comment = factory.make_name('comment') |
1983 | - self.client.post( |
1984 | - self.get_machine_uri(machine), |
1985 | - {'op': 'mark_fixed', 'comment': comment}) |
1986 | - self.assertThat( |
1987 | - machine_mark_fixed, |
1988 | - MockCalledOnceWith(self.logged_in_user, comment)) |
1989 | - |
1990 | - def test_mark_fixed_handles_missing_comment(self): |
1991 | - self.become_admin() |
1992 | - machine = factory.make_Node(status=NODE_STATUS.BROKEN) |
1993 | - machine_mark_fixed = self.patch(node_module.Machine, 'mark_fixed') |
1994 | - self.client.post( |
1995 | - self.get_machine_uri(machine), {'op': 'mark_fixed'}) |
1996 | - self.assertThat( |
1997 | - machine_mark_fixed, |
1998 | - MockCalledOnceWith(self.logged_in_user, None)) |
1999 | - |
2000 | - |
2001 | class TestPowerParameters(APITestCase): |
2002 | def get_machine_uri(self, machine): |
2003 | """Get the API URI for `machine`.""" |
2004 | |
2005 | === modified file 'src/maasserver/api/tests/test_machines.py' |
2006 | --- src/maasserver/api/tests/test_machines.py 2016-02-29 07:45:25 +0000 |
2007 | +++ src/maasserver/api/tests/test_machines.py 2016-03-02 18:21:51 +0000 |
2008 | @@ -107,57 +107,15 @@ |
2009 | name=domainname, defaults={'authoritative': True}) |
2010 | factory.make_Node( |
2011 | hostname=hostname, domain=domain) |
2012 | - expected_hostname = "%s.%s" % (hostname, domainname) |
2013 | + fqdn = "%s.%s" % (hostname, domainname) |
2014 | response = self.client.get(reverse('machines_handler')) |
2015 | self.assertEqual( |
2016 | http.client.OK.value, response.status_code, response.content) |
2017 | parsed_result = json.loads( |
2018 | response.content.decode(settings.DEFAULT_CHARSET)) |
2019 | self.assertItemsEqual( |
2020 | - [expected_hostname], |
2021 | - [machine.get('hostname') for machine in parsed_result]) |
2022 | - |
2023 | - |
2024 | -class AnonymousIsRegisteredAPITest(MAASServerTestCase): |
2025 | - |
2026 | - def test_is_registered_returns_True_if_machine_registered(self): |
2027 | - mac_address = factory.make_mac_address() |
2028 | - factory.make_Interface( |
2029 | - INTERFACE_TYPE.PHYSICAL, mac_address=mac_address) |
2030 | - response = self.client.get( |
2031 | - reverse('machines_handler'), |
2032 | - {'op': 'is_registered', 'mac_address': mac_address}) |
2033 | - self.assertEqual( |
2034 | - (http.client.OK.value, "true"), |
2035 | - (response.status_code, |
2036 | - response.content.decode(settings.DEFAULT_CHARSET))) |
2037 | - |
2038 | - def test_is_registered_normalizes_mac_address(self): |
2039 | - # These two non-normalized MAC addresses are the same. |
2040 | - non_normalized_mac_address = 'AA-bb-cc-dd-ee-ff' |
2041 | - non_normalized_mac_address2 = 'aabbccddeeff' |
2042 | - factory.make_Interface( |
2043 | - INTERFACE_TYPE.PHYSICAL, mac_address=non_normalized_mac_address) |
2044 | - response = self.client.get( |
2045 | - reverse('machines_handler'), |
2046 | - { |
2047 | - 'op': 'is_registered', |
2048 | - 'mac_address': non_normalized_mac_address2 |
2049 | - }) |
2050 | - self.assertEqual( |
2051 | - (http.client.OK.value, "true"), |
2052 | - (response.status_code, |
2053 | - response.content.decode(settings.DEFAULT_CHARSET))) |
2054 | - |
2055 | - def test_is_registered_returns_False_if_machine_not_registered(self): |
2056 | - mac_address = factory.make_mac_address() |
2057 | - response = self.client.get( |
2058 | - reverse('machines_handler'), |
2059 | - {'op': 'is_registered', 'mac_address': mac_address}) |
2060 | - self.assertEqual( |
2061 | - (http.client.OK.value, "false"), |
2062 | - (response.status_code, |
2063 | - response.content.decode(settings.DEFAULT_CHARSET))) |
2064 | + [fqdn], |
2065 | + [machine.get('fqdn') for machine in parsed_result]) |
2066 | |
2067 | |
2068 | def extract_system_ids(parsed_result): |
2069 | @@ -649,7 +607,7 @@ |
2070 | domain_name = desired_machine.domain.name |
2071 | self.assertEqual( |
2072 | "%s.%s" % (desired_machine.hostname, domain_name), |
2073 | - parsed_result['hostname']) |
2074 | + parsed_result['fqdn']) |
2075 | |
2076 | def test_POST_allocate_would_rather_fail_than_disobey_constraint(self): |
2077 | # If "allocate" is passed a constraint, it won't return a machine |
2078 | @@ -693,7 +651,7 @@ |
2079 | self.assertEqual( |
2080 | "%s.%s" % (machine.hostname, domain_name), |
2081 | json.loads( |
2082 | - response.content.decode(settings.DEFAULT_CHARSET))['hostname']) |
2083 | + response.content.decode(settings.DEFAULT_CHARSET))['fqdn']) |
2084 | |
2085 | def test_POST_allocate_treats_unknown_name_as_resource_conflict(self): |
2086 | # A name constraint naming an unknown machine produces a resource |
2087 | @@ -1693,7 +1651,7 @@ |
2088 | self.assertThat( |
2089 | add_chassis, MockCalledOnceWith( |
2090 | self.logged_in_user.username, 'virsh', hostname, None, None, |
2091 | - True, None, None, None, None)) |
2092 | + True, None, None, None, None, None)) |
2093 | |
2094 | def test_POST_add_chassis_sends_accept_all_false_when_not_true(self): |
2095 | self.become_admin() |
2096 | @@ -1716,7 +1674,7 @@ |
2097 | self.assertThat( |
2098 | add_chassis, MockCalledOnceWith( |
2099 | self.logged_in_user.username, 'virsh', hostname, None, None, |
2100 | - False, None, None, None, None)) |
2101 | + False, None, None, None, None, None)) |
2102 | |
2103 | def test_POST_add_chassis_sends_prefix_filter(self): |
2104 | self.become_admin() |
2105 | @@ -1747,8 +1705,8 @@ |
2106 | self.assertThat( |
2107 | add_chassis, MockCalledWith( |
2108 | self.logged_in_user.username, chassis_type, hostname, |
2109 | - username, password, False, prefix_filter, None, None, None |
2110 | - )) |
2111 | + username, password, False, None, prefix_filter, None, |
2112 | + None, None)) |
2113 | |
2114 | def test_POST_add_chassis_only_allows_prefix_filter_on_virtual_chassis( |
2115 | self): |
2116 | @@ -1861,7 +1819,7 @@ |
2117 | self.assertThat( |
2118 | add_chassis, MockCalledWith( |
2119 | self.logged_in_user.username, chassis_type, hostname, |
2120 | - username, password, False, None, None, port, None)) |
2121 | + username, password, False, None, None, None, port, None)) |
2122 | |
2123 | def test_POST_add_chasis_only_allows_port_with_vmware_and_msftocs(self): |
2124 | self.become_admin() |
2125 | @@ -1910,7 +1868,7 @@ |
2126 | self.assertThat( |
2127 | add_chassis, MockCalledWith( |
2128 | self.logged_in_user.username, 'vmware', hostname, username, |
2129 | - password, False, None, None, None, protocol)) |
2130 | + password, False, None, None, None, None, protocol)) |
2131 | |
2132 | def test_POST_add_chasis_only_allows_protocol_with_vmware(self): |
2133 | self.become_admin() |
2134 | @@ -1933,6 +1891,71 @@ |
2135 | ("protocol is unavailable with the %s chassis type" % |
2136 | chassis_type).encode('utf-8'), response.content) |
2137 | |
2138 | + def test_POST_add_chassis_accept_domain_by_name(self): |
2139 | + self.become_admin() |
2140 | + rack = factory.make_RackController() |
2141 | + accessible_by_url = self.patch( |
2142 | + machines_module.RackController.objects, 'get_accessible_by_url') |
2143 | + accessible_by_url.return_value = rack |
2144 | + add_chassis = self.patch(rack, 'add_chassis') |
2145 | + hostname = factory.make_url() |
2146 | + domain = factory.make_Domain() |
2147 | + response = self.client.post( |
2148 | + reverse('machines_handler'), |
2149 | + { |
2150 | + 'op': 'add_chassis', |
2151 | + 'chassis_type': 'virsh', |
2152 | + 'hostname': hostname, |
2153 | + 'domain': domain.name, |
2154 | + }) |
2155 | + self.assertEqual( |
2156 | + http.client.OK, response.status_code, response.content) |
2157 | + self.assertThat( |
2158 | + add_chassis, MockCalledWith( |
2159 | + self.logged_in_user.username, 'virsh', hostname, None, |
2160 | + None, False, domain.name, None, None, None, None)) |
2161 | + |
2162 | + def test_POST_add_chassis_accept_domain_by_id(self): |
2163 | + self.become_admin() |
2164 | + rack = factory.make_RackController() |
2165 | + accessible_by_url = self.patch( |
2166 | + machines_module.RackController.objects, 'get_accessible_by_url') |
2167 | + accessible_by_url.return_value = rack |
2168 | + add_chassis = self.patch(rack, 'add_chassis') |
2169 | + hostname = factory.make_url() |
2170 | + domain = factory.make_Domain() |
2171 | + response = self.client.post( |
2172 | + reverse('machines_handler'), |
2173 | + { |
2174 | + 'op': 'add_chassis', |
2175 | + 'chassis_type': 'virsh', |
2176 | + 'hostname': hostname, |
2177 | + 'domain': domain.id, |
2178 | + }) |
2179 | + self.assertEqual( |
2180 | + http.client.OK, response.status_code, response.content) |
2181 | + self.assertThat( |
2182 | + add_chassis, MockCalledWith( |
2183 | + self.logged_in_user.username, 'virsh', hostname, None, |
2184 | + None, False, domain.name, None, None, None, None)) |
2185 | + |
2186 | + def test_POST_add_chassis_validates_domain(self): |
2187 | + self.become_admin() |
2188 | + domain = factory.make_name('domain') |
2189 | + response = self.client.post( |
2190 | + reverse('machines_handler'), |
2191 | + { |
2192 | + 'op': 'add_chassis', |
2193 | + 'chassis_type': 'virsh', |
2194 | + 'hostname': factory.make_url(), |
2195 | + 'domain': domain, |
2196 | + }) |
2197 | + self.assertEqual( |
2198 | + http.client.NOT_FOUND, response.status_code, response.content) |
2199 | + self.assertEqual( |
2200 | + ("Unable to find specified domain %s" % domain).encode('utf-8'), |
2201 | + response.content) |
2202 | + |
2203 | def test_POST_add_chassis_accepts_system_id_for_rack_controller(self): |
2204 | self.become_admin() |
2205 | subnet = factory.make_Subnet() |
2206 | @@ -1956,7 +1979,7 @@ |
2207 | self.assertThat( |
2208 | add_chassis, MockCalledWith( |
2209 | self.logged_in_user.username, 'virsh', hostname, None, None, |
2210 | - False, None, None, None, None)) |
2211 | + False, None, None, None, None, None)) |
2212 | |
2213 | def test_POST_add_chassis_accepts_hostname_for_rack_controller(self): |
2214 | self.become_admin() |
2215 | @@ -1981,7 +2004,7 @@ |
2216 | self.assertThat( |
2217 | add_chassis, MockCalledWith( |
2218 | self.logged_in_user.username, 'virsh', hostname, None, None, |
2219 | - False, None, None, None, None)) |
2220 | + False, None, None, None, None, None)) |
2221 | |
2222 | def test_POST_add_chassis_rejects_invalid_rack_controller(self): |
2223 | self.become_admin() |
2224 | |
2225 | === modified file 'src/maasserver/api/tests/test_node.py' |
2226 | --- src/maasserver/api/tests/test_node.py 2016-02-26 18:39:26 +0000 |
2227 | +++ src/maasserver/api/tests/test_node.py 2016-03-02 18:21:51 +0000 |
2228 | @@ -6,18 +6,13 @@ |
2229 | __all__ = [] |
2230 | |
2231 | import http.client |
2232 | -from io import StringIO |
2233 | -import sys |
2234 | |
2235 | import bson |
2236 | from django.conf import settings |
2237 | from django.core.urlresolvers import reverse |
2238 | from maasserver.enum import ( |
2239 | - INTERFACE_TYPE, |
2240 | - IPADDRESS_TYPE, |
2241 | NODE_STATUS, |
2242 | NODE_STATUS_CHOICES, |
2243 | - NODE_STATUS_CHOICES_DICT, |
2244 | ) |
2245 | from maasserver.models import ( |
2246 | Node, |
2247 | @@ -44,24 +39,10 @@ |
2248 | |
2249 | class NodeAnonAPITest(MAASServerTestCase): |
2250 | |
2251 | - def setUp(self): |
2252 | - super(NodeAnonAPITest, self).setUp() |
2253 | - self.patch(node_module, 'power_on_node') |
2254 | - self.patch(node_module, 'power_off_node') |
2255 | - self.patch(node_module, 'power_driver_check') |
2256 | - |
2257 | - def test_anon_api_doc(self): |
2258 | - # The documentation is accessible to anon users. |
2259 | - self.patch(sys, "stderr", StringIO()) |
2260 | - response = self.client.get(reverse('api-doc')) |
2261 | - self.assertEqual(http.client.OK, response.status_code) |
2262 | - # No error or warning are emitted by docutils. |
2263 | - self.assertEqual("", sys.stderr.getvalue()) |
2264 | - |
2265 | def test_node_init_user_cannot_access(self): |
2266 | token = NodeKey.objects.get_token_for_node(factory.make_Node()) |
2267 | client = OAuthAuthenticatedClient(get_node_init_user(), token) |
2268 | - response = client.get(reverse('nodes_handler'), {'op': 'list'}) |
2269 | + response = client.get(reverse('nodes_handler')) |
2270 | self.assertEqual(http.client.FORBIDDEN, response.status_code) |
2271 | |
2272 | |
2273 | @@ -87,13 +68,6 @@ |
2274 | class TestNodeAPI(APITestCase): |
2275 | """Tests for /api/2.0/nodes/<node>/.""" |
2276 | |
2277 | - def setUp(self): |
2278 | - super(TestNodeAPI, self).setUp() |
2279 | - self.patch(node_module, 'power_on_node') |
2280 | - self.patch(node_module, 'power_off_node') |
2281 | - self.patch(node_module, 'power_driver_check') |
2282 | - self.patch(node_module.Node, '_power_control_node') |
2283 | - |
2284 | def test_handler_path(self): |
2285 | self.assertEqual( |
2286 | '/api/2.0/nodes/node-name/', |
2287 | @@ -104,62 +78,6 @@ |
2288 | """Get the API URI for `node`.""" |
2289 | return reverse('node_handler', args=[node.system_id]) |
2290 | |
2291 | - def test_GET_returns_node(self): |
2292 | - # The api allows for fetching a single Node (using system_id). |
2293 | - node = factory.make_Node() |
2294 | - response = self.client.get(self.get_node_uri(node)) |
2295 | - |
2296 | - self.assertEqual(http.client.OK, response.status_code) |
2297 | - parsed_result = json_load_bytes(response.content) |
2298 | - domain_name = node.domain.name |
2299 | - self.assertEqual( |
2300 | - "%s.%s" % (node.hostname, domain_name), |
2301 | - parsed_result['hostname']) |
2302 | - self.assertEqual(node.system_id, parsed_result['system_id']) |
2303 | - |
2304 | - def test_GET_returns_associated_tag(self): |
2305 | - node = factory.make_Node() |
2306 | - tag = factory.make_Tag() |
2307 | - node.tags.add(tag) |
2308 | - response = self.client.get(self.get_node_uri(node)) |
2309 | - |
2310 | - self.assertEqual(http.client.OK, response.status_code) |
2311 | - parsed_result = json_load_bytes(response.content) |
2312 | - self.assertEqual([tag.name], parsed_result['tag_names']) |
2313 | - |
2314 | - def test_GET_returns_associated_ip_addresses(self): |
2315 | - node = factory.make_Node(disable_ipv4=False) |
2316 | - nic = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
2317 | - subnet = factory.make_Subnet() |
2318 | - ip = factory.pick_ip_in_network(subnet.get_ipnetwork()) |
2319 | - lease = factory.make_StaticIPAddress( |
2320 | - alloc_type=IPADDRESS_TYPE.DISCOVERED, ip=ip, |
2321 | - interface=nic, subnet=subnet) |
2322 | - response = self.client.get(self.get_node_uri(node)) |
2323 | - |
2324 | - self.assertEqual( |
2325 | - http.client.OK, response.status_code, response.content) |
2326 | - parsed_result = json_load_bytes(response.content) |
2327 | - self.assertEqual([lease.ip], parsed_result['ip_addresses']) |
2328 | - |
2329 | - def test_GET_returns_interface_set(self): |
2330 | - node = factory.make_Node() |
2331 | - response = self.client.get(self.get_node_uri(node)) |
2332 | - self.assertEqual(http.client.OK, response.status_code) |
2333 | - parsed_result = json_load_bytes(response.content) |
2334 | - self.assertIn('interface_set', parsed_result) |
2335 | - |
2336 | - def test_GET_returns_zone(self): |
2337 | - node = factory.make_Node() |
2338 | - response = self.client.get(self.get_node_uri(node)) |
2339 | - self.assertEqual(http.client.OK, response.status_code) |
2340 | - parsed_result = json_load_bytes(response.content) |
2341 | - self.assertEqual( |
2342 | - [node.zone.name, node.zone.description], |
2343 | - [ |
2344 | - parsed_result['zone']['name'], |
2345 | - parsed_result['zone']['description']]) |
2346 | - |
2347 | def test_GET_refuses_to_access_nonexistent_node(self): |
2348 | # When fetching a Node, the api returns a 'Not Found' (404) error |
2349 | # if no node is found. |
2350 | @@ -182,84 +100,6 @@ |
2351 | self.assertEqual( |
2352 | "Not Found", response.content.decode(settings.DEFAULT_CHARSET)) |
2353 | |
2354 | - def test_GET_returns_owner_name_when_allocated_to_self(self): |
2355 | - node = factory.make_Node( |
2356 | - status=NODE_STATUS.ALLOCATED, owner=self.logged_in_user) |
2357 | - response = self.client.get(self.get_node_uri(node)) |
2358 | - self.assertEqual(http.client.OK, response.status_code) |
2359 | - parsed_result = json_load_bytes(response.content) |
2360 | - self.assertEqual(node.owner.username, parsed_result["owner"]) |
2361 | - |
2362 | - def test_GET_returns_owner_name_when_allocated_to_other_user(self): |
2363 | - node = factory.make_Node( |
2364 | - status=NODE_STATUS.ALLOCATED, owner=factory.make_User()) |
2365 | - response = self.client.get(self.get_node_uri(node)) |
2366 | - self.assertEqual(http.client.OK, response.status_code) |
2367 | - parsed_result = json_load_bytes(response.content) |
2368 | - self.assertEqual(node.owner.username, parsed_result["owner"]) |
2369 | - |
2370 | - def test_GET_returns_empty_owner_when_not_allocated(self): |
2371 | - node = factory.make_Node(status=NODE_STATUS.READY) |
2372 | - response = self.client.get(self.get_node_uri(node)) |
2373 | - self.assertEqual(http.client.OK, response.status_code) |
2374 | - parsed_result = json_load_bytes(response.content) |
2375 | - self.assertEqual(None, parsed_result["owner"]) |
2376 | - |
2377 | - def test_GET_returns_physical_block_devices(self): |
2378 | - node = factory.make_Node(with_boot_disk=False) |
2379 | - devices = [ |
2380 | - factory.make_PhysicalBlockDevice(node=node) |
2381 | - for _ in range(3) |
2382 | - ] |
2383 | - response = self.client.get(self.get_node_uri(node)) |
2384 | - self.assertEqual(http.client.OK, response.status_code) |
2385 | - parsed_result = json_load_bytes(response.content) |
2386 | - parsed_devices = [ |
2387 | - device['name'] |
2388 | - for device in parsed_result['physicalblockdevice_set'] |
2389 | - ] |
2390 | - self.assertItemsEqual( |
2391 | - [device.name for device in devices], parsed_devices) |
2392 | - |
2393 | - def test_GET_returns_min_hwe_kernel_and_hwe_kernel(self): |
2394 | - node = factory.make_Node() |
2395 | - response = self.client.get(self.get_node_uri(node)) |
2396 | - |
2397 | - self.assertEqual(http.client.OK, response.status_code) |
2398 | - parsed_result = json_load_bytes(response.content) |
2399 | - self.assertEqual(None, parsed_result['min_hwe_kernel']) |
2400 | - self.assertEqual(None, parsed_result['hwe_kernel']) |
2401 | - |
2402 | - def test_GET_returns_min_hwe_kernel(self): |
2403 | - node = factory.make_Node(min_hwe_kernel="hwe-v") |
2404 | - response = self.client.get(self.get_node_uri(node)) |
2405 | - |
2406 | - self.assertEqual(http.client.OK, response.status_code) |
2407 | - parsed_result = json_load_bytes(response.content) |
2408 | - self.assertEqual("hwe-v", parsed_result['min_hwe_kernel']) |
2409 | - |
2410 | - def test_GET_returns_status_message_with_most_recent_event(self): |
2411 | - """Makes sure the most recent event from this node is shown in the |
2412 | - status_message attribute.""" |
2413 | - # The first event won't be returned. |
2414 | - event = factory.make_Event(description="Uninteresting event") |
2415 | - node = event.node |
2416 | - # The second (and last) event will be returned. |
2417 | - message = "Interesting event" |
2418 | - factory.make_Event(description=message, node=node) |
2419 | - response = self.client.get(self.get_node_uri(node)) |
2420 | - parsed_result = json_load_bytes(response.content) |
2421 | - self.assertEqual(message, parsed_result['status_message']) |
2422 | - |
2423 | - def test_GET_returns_status_name(self): |
2424 | - """GET should display the node status as a user-friendly string.""" |
2425 | - for status in NODE_STATUS_CHOICES_DICT: |
2426 | - node = factory.make_Node(status=status) |
2427 | - response = self.client.get(self.get_node_uri(node)) |
2428 | - parsed_result = json_load_bytes(response.content) |
2429 | - self.assertEqual(NODE_STATUS_CHOICES_DICT[status], |
2430 | - parsed_result['status_name']) |
2431 | - |
2432 | def test_resource_uri_points_back_at_machine(self): |
2433 | self.become_admin() |
2434 | # When a Machine is returned by the API, the field 'resource_uri' |
2435 | |
2436 | === modified file 'src/maasserver/api/tests/test_nodes.py' |
2437 | --- src/maasserver/api/tests/test_nodes.py 2016-02-26 18:39:26 +0000 |
2438 | +++ src/maasserver/api/tests/test_nodes.py 2016-03-02 18:21:51 +0000 |
2439 | @@ -20,10 +20,7 @@ |
2440 | NODE_TYPE, |
2441 | ) |
2442 | from maasserver.exceptions import MAASAPIValidationError |
2443 | -from maasserver.testing.api import ( |
2444 | - APITestCase, |
2445 | - MultipleUsersScenarios, |
2446 | -) |
2447 | +from maasserver.testing.api import APITestCase |
2448 | from maasserver.testing.factory import factory |
2449 | from maasserver.testing.testcase import MAASServerTestCase |
2450 | from maasserver.utils import ignore_unused |
2451 | @@ -31,33 +28,6 @@ |
2452 | from maastesting.djangotestcase import count_queries |
2453 | |
2454 | |
2455 | -class NodeHostnameTest(MultipleUsersScenarios, |
2456 | - MAASServerTestCase): |
2457 | - |
2458 | - scenarios = [ |
2459 | - ('user', dict(userfactory=factory.make_User)), |
2460 | - ('admin', dict(userfactory=factory.make_admin)), |
2461 | - ] |
2462 | - |
2463 | - def test_GET_returns_fqdn_with_proper_domain_name(self): |
2464 | - # The FQDN for a host is properly constructed. |
2465 | - hostname_without_domain = factory.make_name('hostname') |
2466 | - domain = factory.make_Domain(name=factory.make_name('domain')) |
2467 | - hostname_with_domain = '%s.%s' % ( |
2468 | - hostname_without_domain, domain.name) |
2469 | - factory.make_Node( |
2470 | - hostname=hostname_with_domain) |
2471 | - expected_hostname = hostname_with_domain |
2472 | - response = self.client.get(reverse('nodes_handler')) |
2473 | - self.assertEqual( |
2474 | - http.client.OK.value, response.status_code, response.content) |
2475 | - parsed_result = json.loads( |
2476 | - response.content.decode(settings.DEFAULT_CHARSET)) |
2477 | - self.assertItemsEqual( |
2478 | - [expected_hostname], |
2479 | - [node.get('hostname') for node in parsed_result]) |
2480 | - |
2481 | - |
2482 | class AnonymousIsRegisteredAPITest(MAASServerTestCase): |
2483 | |
2484 | def test_is_registered_returns_True_if_node_registered(self): |
2485 | |
2486 | === modified file 'src/maasserver/api/tests/test_rackcontroller.py' |
2487 | --- src/maasserver/api/tests/test_rackcontroller.py 2016-02-18 00:34:58 +0000 |
2488 | +++ src/maasserver/api/tests/test_rackcontroller.py 2016-03-02 18:21:51 +0000 |
2489 | @@ -12,6 +12,7 @@ |
2490 | explain_unexpected_response, |
2491 | ) |
2492 | from maasserver.testing.factory import factory |
2493 | +from maasserver.utils.converters import json_load_bytes |
2494 | from maastesting.matchers import MockCalledOnceWith |
2495 | |
2496 | |
2497 | @@ -74,7 +75,28 @@ |
2498 | self.assertEqual( |
2499 | '/api/2.0/rackcontrollers/', reverse('rackcontrollers_handler')) |
2500 | |
2501 | - @staticmethod |
2502 | - def get_rack_uri(rack): |
2503 | - """Get the API URI for `rack`.""" |
2504 | - return reverse('rackcontrollers_handler') |
2505 | + def test_read_returns_limited_fields(self): |
2506 | + factory.make_RackController(owner=self.logged_in_user) |
2507 | + response = self.client.get(reverse('rackcontrollers_handler')) |
2508 | + parsed_result = json_load_bytes(response.content) |
2509 | + self.assertItemsEqual( |
2510 | + [ |
2511 | + 'system_id', |
2512 | + 'hostname', |
2513 | + 'domain', |
2514 | + 'fqdn', |
2515 | + 'architecture', |
2516 | + 'cpu_count', |
2517 | + 'memory', |
2518 | + 'swap_size', |
2519 | + 'osystem', |
2520 | + 'resource_uri', |
2521 | + 'distro_series', |
2522 | + 'interface_set', |
2523 | + 'ip_addresses', |
2524 | + 'zone', |
2525 | + 'status_action', |
2526 | + 'node_type', |
2527 | + 'node_type_name', |
2528 | + ], |
2529 | + list(parsed_result[0])) |
2530 | |
2531 | === modified file 'src/maasserver/api/users.py' |
2532 | --- src/maasserver/api/users.py 2016-02-03 10:27:11 +0000 |
2533 | +++ src/maasserver/api/users.py 2016-03-02 18:21:51 +0000 |
2534 | @@ -1,4 +1,4 @@ |
2535 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
2536 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
2537 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2538 | |
2539 | """API handlers: `User`.""" |
2540 | |
2541 | === modified file 'src/maasserver/api/utils.py' |
2542 | --- src/maasserver/api/utils.py 2015-12-01 18:12:59 +0000 |
2543 | +++ src/maasserver/api/utils.py 2016-03-02 18:21:51 +0000 |
2544 | @@ -1,4 +1,4 @@ |
2545 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
2546 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
2547 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2548 | |
2549 | """Helpers for Piston-based MAAS APIs.""" |
2550 | |
2551 | === modified file 'src/maasserver/api/version.py' |
2552 | --- src/maasserver/api/version.py 2015-12-11 20:45:50 +0000 |
2553 | +++ src/maasserver/api/version.py 2016-03-02 18:21:51 +0000 |
2554 | @@ -1,4 +1,4 @@ |
2555 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
2556 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
2557 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2558 | |
2559 | """API handler: API Version.""" |
2560 | |
2561 | === modified file 'src/maasserver/api/volume_groups.py' |
2562 | --- src/maasserver/api/volume_groups.py 2015-12-16 00:01:56 +0000 |
2563 | +++ src/maasserver/api/volume_groups.py 2016-03-02 18:21:51 +0000 |
2564 | @@ -1,4 +1,4 @@ |
2565 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
2566 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
2567 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2568 | |
2569 | """API handlers: `VolumeGroups`.""" |
2570 | @@ -46,7 +46,7 @@ |
2571 | |
2572 | |
2573 | class VolumeGroupsHandler(OperationsHandler): |
2574 | - """Manage volume groups on a node.""" |
2575 | + """Manage volume groups on a machine.""" |
2576 | api_doc_section_name = "Volume groups" |
2577 | update = delete = None |
2578 | fields = DISPLAYED_VOLUME_GROUP_FIELDS |
2579 | @@ -80,7 +80,7 @@ |
2580 | system_id, request.user, NODE_PERMISSION.ADMIN) |
2581 | if machine.status != NODE_STATUS.READY: |
2582 | raise NodeStateViolation( |
2583 | - "Cannot create volume group because the node is not Ready.") |
2584 | + "Cannot create volume group because the machine is not Ready.") |
2585 | form = CreateVolumeGroupForm(machine, data=request.data) |
2586 | if not form.is_valid(): |
2587 | raise MAASAPIValidationError(form.errors) |
2588 | @@ -89,7 +89,7 @@ |
2589 | |
2590 | |
2591 | class VolumeGroupHandler(OperationsHandler): |
2592 | - """Manage volume group on a node.""" |
2593 | + """Manage volume group on a machine.""" |
2594 | api_doc_section_name = "Volume group" |
2595 | create = None |
2596 | model = VolumeGroup |
2597 | @@ -143,15 +143,15 @@ |
2598 | ] |
2599 | |
2600 | def read(self, request, system_id, volume_group_id): |
2601 | - """Read volume group on node. |
2602 | + """Read volume group on a machine. |
2603 | |
2604 | - Returns 404 if the node or volume group is not found. |
2605 | + Returns 404 if the machine or volume group is not found. |
2606 | """ |
2607 | return VolumeGroup.objects.get_object_or_404( |
2608 | system_id, volume_group_id, request.user, NODE_PERMISSION.VIEW) |
2609 | |
2610 | def update(self, request, system_id, volume_group_id): |
2611 | - """Read volume group on node. |
2612 | + """Read volume group on a machine. |
2613 | |
2614 | :param name: Name of the volume group. |
2615 | :param uuid: UUID of the volume group. |
2616 | @@ -161,15 +161,15 @@ |
2617 | :param add_partitions: Partitions to add to the volume group. |
2618 | :param remove_partitions: Partitions to remove from the volume group. |
2619 | |
2620 | - Returns 404 if the node or volume group is not found. |
2621 | - Returns 409 if the node is not Ready. |
2622 | + Returns 404 if the machine or volume group is not found. |
2623 | + Returns 409 if the machine is not Ready. |
2624 | """ |
2625 | volume_group = VolumeGroup.objects.get_object_or_404( |
2626 | system_id, volume_group_id, request.user, NODE_PERMISSION.ADMIN) |
2627 | node = volume_group.get_node() |
2628 | if node.status != NODE_STATUS.READY: |
2629 | raise NodeStateViolation( |
2630 | - "Cannot update volume group because the node is not Ready.") |
2631 | + "Cannot update volume group because the machine is not Ready.") |
2632 | form = UpdateVolumeGroupForm(volume_group, data=request.data) |
2633 | if not form.is_valid(): |
2634 | raise MAASAPIValidationError(form.errors) |
2635 | @@ -177,17 +177,17 @@ |
2636 | return form.save() |
2637 | |
2638 | def delete(self, request, system_id, volume_group_id): |
2639 | - """Delete volume group on node. |
2640 | + """Delete volume group on a machine. |
2641 | |
2642 | - Returns 404 if the node or volume group is not found. |
2643 | - Returns 409 if the node is not Ready. |
2644 | + Returns 404 if the machine or volume group is not found. |
2645 | + Returns 409 if the machine is not Ready. |
2646 | """ |
2647 | volume_group = VolumeGroup.objects.get_object_or_404( |
2648 | system_id, volume_group_id, request.user, NODE_PERMISSION.ADMIN) |
2649 | node = volume_group.get_node() |
2650 | if node.status != NODE_STATUS.READY: |
2651 | raise NodeStateViolation( |
2652 | - "Cannot delete volume group because the node is not Ready.") |
2653 | + "Cannot delete volume group because the machine is not Ready.") |
2654 | volume_group.delete() |
2655 | return rc.DELETED |
2656 | |
2657 | @@ -199,15 +199,16 @@ |
2658 | :param uuid: (optional) UUID of the logical volume. |
2659 | :param size: Size of the logical volume. |
2660 | |
2661 | - Returns 404 if the node or volume group is not found. |
2662 | - Returns 409 if the node is not Ready. |
2663 | + Returns 404 if the machine or volume group is not found. |
2664 | + Returns 409 if the machine is not Ready. |
2665 | """ |
2666 | volume_group = VolumeGroup.objects.get_object_or_404( |
2667 | system_id, volume_group_id, request.user, NODE_PERMISSION.ADMIN) |
2668 | node = volume_group.get_node() |
2669 | if node.status != NODE_STATUS.READY: |
2670 | raise NodeStateViolation( |
2671 | - "Cannot create logical volume because the node is not Ready.") |
2672 | + "Cannot create logical volume because the machine is not " |
2673 | + "Ready.") |
2674 | form = CreateLogicalVolumeForm(volume_group, data=request.data) |
2675 | if not form.is_valid(): |
2676 | raise MAASAPIValidationError(form.errors) |
2677 | @@ -221,15 +222,16 @@ |
2678 | :param id: ID of the logical volume. |
2679 | |
2680 | Returns 403 if no logical volume with id. |
2681 | - Returns 404 if the node or volume group is not found. |
2682 | - Returns 409 if the node is not Ready. |
2683 | + Returns 404 if the machine or volume group is not found. |
2684 | + Returns 409 if the machine is not Ready. |
2685 | """ |
2686 | volume_group = VolumeGroup.objects.get_object_or_404( |
2687 | system_id, volume_group_id, request.user, NODE_PERMISSION.ADMIN) |
2688 | node = volume_group.get_node() |
2689 | if node.status != NODE_STATUS.READY: |
2690 | raise NodeStateViolation( |
2691 | - "Cannot delete logical volume because the node is not Ready.") |
2692 | + "Cannot delete logical volume because the machine is not " |
2693 | + "Ready.") |
2694 | volume_id = get_mandatory_param(request.data, 'id') |
2695 | try: |
2696 | logical_volume = volume_group.virtual_devices.get(id=volume_id) |
2697 | |
2698 | === modified file 'src/maasserver/api/zones.py' |
2699 | --- src/maasserver/api/zones.py 2015-12-01 18:12:59 +0000 |
2700 | +++ src/maasserver/api/zones.py 2016-03-02 18:21:51 +0000 |
2701 | @@ -1,4 +1,4 @@ |
2702 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
2703 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
2704 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2705 | |
2706 | """API handlers: `Zone`.""" |
2707 | |
2708 | === modified file 'src/maasserver/forms.py' |
2709 | --- src/maasserver/forms.py 2016-03-01 21:59:22 +0000 |
2710 | +++ src/maasserver/forms.py 2016-03-02 18:21:51 +0000 |
2711 | @@ -1,11 +1,12 @@ |
2712 | -# Copyright 2012-2015 Canonical Ltd. This software is licensed under the |
2713 | +# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
2714 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2715 | |
2716 | """Forms.""" |
2717 | |
2718 | __all__ = [ |
2719 | + "AdminMachineForm", |
2720 | + "AdminMachineWithMACAddressesForm", |
2721 | "AdminNodeForm", |
2722 | - "AdminNodeWithMACAddressesForm", |
2723 | "BootSourceForm", |
2724 | "BootSourceSelectionForm", |
2725 | "BootSourceSettingsForm", |
2726 | @@ -13,14 +14,17 @@ |
2727 | "ClaimIPForMACForm", |
2728 | "CommissioningForm", |
2729 | "CommissioningScriptForm", |
2730 | + "get_machine_edit_form", |
2731 | + "get_machine_create_form", |
2732 | "CreatePhysicalBlockDeviceForm", |
2733 | - "get_node_create_form", |
2734 | "get_node_edit_form", |
2735 | "list_all_usable_architectures", |
2736 | "MAASAndNetworkForm", |
2737 | "MountFilesystemForm", |
2738 | "NetworksListingForm", |
2739 | - "NodeWithMACAddressesForm", |
2740 | + "NodeChoiceField", |
2741 | + "MachineWithMACAddressesForm", |
2742 | + "CreatePhysicalBlockDeviceForm", |
2743 | "ReleaseIPForm", |
2744 | "SSHKeyForm", |
2745 | "SSLKeyForm", |
2746 | @@ -102,10 +106,12 @@ |
2747 | CacheSet, |
2748 | Config, |
2749 | Device, |
2750 | + Domain, |
2751 | Filesystem, |
2752 | Interface, |
2753 | LargeFile, |
2754 | LicenseKey, |
2755 | + Machine, |
2756 | Node, |
2757 | Partition, |
2758 | PartitionTable, |
2759 | @@ -371,10 +377,10 @@ |
2760 | class NodeForm(MAASModelForm): |
2761 | def __init__(self, request=None, *args, **kwargs): |
2762 | super(NodeForm, self).__init__(*args, **kwargs) |
2763 | + |
2764 | # Even though it doesn't need it and doesn't use it, this form accepts |
2765 | # a parameter named 'request' because it is used interchangingly |
2766 | - # with AdminNodeForm which actually uses this parameter. |
2767 | - |
2768 | + # with AdminMachineForm which actually uses this parameter. |
2769 | instance = kwargs.get('instance') |
2770 | if instance is None or instance.owner is None: |
2771 | self.has_owner = False |
2772 | @@ -384,78 +390,9 @@ |
2773 | # Are we creating a new node object? |
2774 | self.new_node = (instance is None) |
2775 | |
2776 | - self.set_up_architecture_field() |
2777 | - if self.has_owner: |
2778 | - self.set_up_osystem_and_distro_series_fields(instance) |
2779 | - |
2780 | self.fields['disable_ipv4'] = forms.BooleanField( |
2781 | label="", required=False) |
2782 | |
2783 | - # We only want the license key field to render in the UI if the `OS` |
2784 | - # and `Release` fields are also present. |
2785 | - if self.has_owner: |
2786 | - self.fields['license_key'] = forms.CharField( |
2787 | - label="License Key", required=False, help_text=( |
2788 | - "License key for operating system"), |
2789 | - max_length=30) |
2790 | - else: |
2791 | - self.fields['license_key'] = forms.CharField( |
2792 | - label="", required=False, widget=forms.HiddenInput()) |
2793 | - |
2794 | - def set_up_architecture_field(self): |
2795 | - """Create the `architecture` field. |
2796 | - |
2797 | - This needs to be done on the fly so that we can pass a dynamic list of |
2798 | - usable architectures. |
2799 | - """ |
2800 | - architectures = list_all_usable_architectures() |
2801 | - default_arch = pick_default_architecture(architectures) |
2802 | - if len(architectures) == 0: |
2803 | - choices = [BLANK_CHOICE] |
2804 | - else: |
2805 | - choices = list_architecture_choices(architectures) |
2806 | - invalid_arch_message = compose_invalid_choice_text( |
2807 | - 'architecture', choices) |
2808 | - self.fields['architecture'] = forms.ChoiceField( |
2809 | - choices=choices, required=False, initial=default_arch, |
2810 | - error_messages={'invalid_choice': invalid_arch_message}) |
2811 | - |
2812 | - def set_up_osystem_and_distro_series_fields(self, instance): |
2813 | - """Create the `osystem` and `distro_series` fields. |
2814 | - |
2815 | - This needs to be done on the fly so that we can pass a dynamic list of |
2816 | - usable operating systems and distro_series. |
2817 | - """ |
2818 | - osystems = list_all_usable_osystems() |
2819 | - releases = list_all_usable_releases(osystems) |
2820 | - if self.has_owner: |
2821 | - os_choices = list_osystem_choices(osystems) |
2822 | - distro_choices = list_release_choices(releases) |
2823 | - invalid_osystem_message = compose_invalid_choice_text( |
2824 | - 'osystem', os_choices) |
2825 | - invalid_distro_series_message = compose_invalid_choice_text( |
2826 | - 'distro_series', distro_choices) |
2827 | - self.fields['osystem'] = forms.ChoiceField( |
2828 | - label="OS", choices=os_choices, required=False, initial='', |
2829 | - error_messages={'invalid_choice': invalid_osystem_message}) |
2830 | - self.fields['distro_series'] = forms.ChoiceField( |
2831 | - label="Release", choices=distro_choices, |
2832 | - required=False, initial='', |
2833 | - error_messages={ |
2834 | - 'invalid_choice': invalid_distro_series_message}) |
2835 | - else: |
2836 | - self.fields['osystem'] = forms.ChoiceField( |
2837 | - label="", required=False, widget=forms.HiddenInput()) |
2838 | - self.fields['distro_series'] = forms.ChoiceField( |
2839 | - label="", required=False, widget=forms.HiddenInput()) |
2840 | - if instance is not None: |
2841 | - initial_value = get_distro_series_initial(osystems, instance) |
2842 | - if instance is not None: |
2843 | - self.initial['distro_series'] = initial_value |
2844 | - |
2845 | - def clean_distro_series(self): |
2846 | - return clean_distro_series_field(self, 'distro_series', 'osystem') |
2847 | - |
2848 | def clean_disable_ipv4(self): |
2849 | # Boolean fields only show up in UI form submissions as "true" (if the |
2850 | # box was checked) or not at all (if the box was not checked). This |
2851 | @@ -495,6 +432,122 @@ |
2852 | except ValueError: |
2853 | raise ValidationError('Invalid size for swap: %s' % swap_size) |
2854 | |
2855 | + def clean_domain(self): |
2856 | + domain = self.cleaned_data.get('domain') |
2857 | + if not domain: |
2858 | + return None |
2859 | + try: |
2860 | + return Domain.objects.get(id=int(domain)) |
2861 | + except ValueError: |
2862 | + try: |
2863 | + return Domain.objects.get(name=domain) |
2864 | + except Domain.DoesNotExist: |
2865 | + raise ValidationError("Unable to find domain %s" % domain) |
2866 | + |
2867 | + hostname = forms.CharField( |
2868 | + label="Host name", required=False, help_text=( |
2869 | + "The hostname of the machine")) |
2870 | + |
2871 | + domain = forms.CharField( |
2872 | + label="Domain name", required=False, help_text=( |
2873 | + "The domain name of the machine.")) |
2874 | + |
2875 | + swap_size = forms.CharField( |
2876 | + label="Swap size", required=False, help_text=( |
2877 | + "The size of the swap file in bytes. The field also accepts K, M, " |
2878 | + "G and T meaning kilobytes, megabytes, gigabytes and terabytes.")) |
2879 | + |
2880 | + class Meta: |
2881 | + model = Node |
2882 | + |
2883 | + # Fields that the form should generate automatically from the |
2884 | + # model: |
2885 | + # Note: fields have to be added here even if they were defined manually |
2886 | + # elsewhere in the form |
2887 | + fields = ( |
2888 | + 'hostname', |
2889 | + 'domain', |
2890 | + 'disable_ipv4', |
2891 | + 'swap_size', |
2892 | + ) |
2893 | + |
2894 | + |
2895 | +class MachineForm(NodeForm): |
2896 | + def __init__(self, request=None, *args, **kwargs): |
2897 | + super(MachineForm, self).__init__(*args, **kwargs) |
2898 | + |
2899 | + # Even though it doesn't need it and doesn't use it, this form accepts |
2900 | + # a parameter named 'request' because it is used interchangingly |
2901 | + # with AdminMachineForm which actually uses this parameter. |
2902 | + instance = kwargs.get('instance') |
2903 | + |
2904 | + self.set_up_architecture_field() |
2905 | + # We only want the license key field to render in the UI if the `OS` |
2906 | + # and `Release` fields are also present. |
2907 | + if self.has_owner: |
2908 | + self.set_up_osystem_and_distro_series_fields(instance) |
2909 | + self.fields['license_key'] = forms.CharField( |
2910 | + label="License Key", required=False, help_text=( |
2911 | + "License key for operating system"), |
2912 | + max_length=30) |
2913 | + else: |
2914 | + self.fields['license_key'] = forms.CharField( |
2915 | + label="", required=False, widget=forms.HiddenInput()) |
2916 | + |
2917 | + def set_up_architecture_field(self): |
2918 | + """Create the `architecture` field. |
2919 | + |
2920 | + This needs to be done on the fly so that we can pass a dynamic list of |
2921 | + usable architectures. |
2922 | + """ |
2923 | + architectures = list_all_usable_architectures() |
2924 | + default_arch = pick_default_architecture(architectures) |
2925 | + if len(architectures) == 0: |
2926 | + choices = [BLANK_CHOICE] |
2927 | + else: |
2928 | + choices = list_architecture_choices(architectures) |
2929 | + invalid_arch_message = compose_invalid_choice_text( |
2930 | + 'architecture', choices) |
2931 | + self.fields['architecture'] = forms.ChoiceField( |
2932 | + choices=choices, required=False, initial=default_arch, |
2933 | + error_messages={'invalid_choice': invalid_arch_message}) |
2934 | + |
2935 | + def set_up_osystem_and_distro_series_fields(self, instance): |
2936 | + """Create the `osystem` and `distro_series` fields. |
2937 | + |
2938 | + This needs to be done on the fly so that we can pass a dynamic list of |
2939 | + usable operating systems and distro_series. |
2940 | + """ |
2941 | + osystems = list_all_usable_osystems() |
2942 | + releases = list_all_usable_releases(osystems) |
2943 | + if self.has_owner: |
2944 | + os_choices = list_osystem_choices(osystems) |
2945 | + distro_choices = list_release_choices(releases) |
2946 | + invalid_osystem_message = compose_invalid_choice_text( |
2947 | + 'osystem', os_choices) |
2948 | + invalid_distro_series_message = compose_invalid_choice_text( |
2949 | + 'distro_series', distro_choices) |
2950 | + self.fields['osystem'] = forms.ChoiceField( |
2951 | + label="OS", choices=os_choices, required=False, initial='', |
2952 | + error_messages={'invalid_choice': invalid_osystem_message}) |
2953 | + self.fields['distro_series'] = forms.ChoiceField( |
2954 | + label="Release", choices=distro_choices, |
2955 | + required=False, initial='', |
2956 | + error_messages={ |
2957 | + 'invalid_choice': invalid_distro_series_message}) |
2958 | + else: |
2959 | + self.fields['osystem'] = forms.ChoiceField( |
2960 | + label="", required=False, widget=forms.HiddenInput()) |
2961 | + self.fields['distro_series'] = forms.ChoiceField( |
2962 | + label="", required=False, widget=forms.HiddenInput()) |
2963 | + if instance is not None: |
2964 | + initial_value = get_distro_series_initial(osystems, instance) |
2965 | + if instance is not None: |
2966 | + self.initial['distro_series'] = initial_value |
2967 | + |
2968 | + def clean_distro_series(self): |
2969 | + return clean_distro_series_field(self, 'distro_series', 'osystem') |
2970 | + |
2971 | def clean_min_hwe_kernel(self): |
2972 | min_hwe_kernel = self.cleaned_data.get('min_hwe_kernel') |
2973 | if self.new_node and not min_hwe_kernel: |
2974 | @@ -503,7 +556,7 @@ |
2975 | return validate_min_hwe_kernel(min_hwe_kernel) |
2976 | |
2977 | def clean(self): |
2978 | - cleaned_data = super(NodeForm, self).clean() |
2979 | + cleaned_data = super(MachineForm, self).clean() |
2980 | |
2981 | if not self.instance.hwe_kernel: |
2982 | osystem = cleaned_data.get('osystem') |
2983 | @@ -520,7 +573,7 @@ |
2984 | return cleaned_data |
2985 | |
2986 | def is_valid(self): |
2987 | - is_valid = super(NodeForm, self).is_valid() |
2988 | + is_valid = super(MachineForm, self).is_valid() |
2989 | if not is_valid: |
2990 | return False |
2991 | if len(list_all_usable_architectures()) == 0: |
2992 | @@ -585,41 +638,20 @@ |
2993 | self.is_bound = True |
2994 | self.data['hwe_kernel'] = hwe_kernel |
2995 | |
2996 | - hostname = forms.CharField( |
2997 | - label="Host name", required=False, help_text=( |
2998 | - "The FQDN (Fully Qualified Domain Name) is derived from the " |
2999 | - "host name: If the cluster controller for this node is managing " |
3000 | - "DNS then the domain part in the host name (if any) is replaced " |
3001 | - "by the domain defined on the cluster; if the cluster controller " |
3002 | - "does not manage DNS, then the host name as entered will be the " |
3003 | - "FQDN.")) |
3004 | - |
3005 | - swap_size = forms.CharField( |
3006 | - label="Swap size", required=False, help_text=( |
3007 | - "The size of the swap file in bytes. The field also accepts K, M, " |
3008 | - "G and T meaning kilobytes, megabytes, gigabytes and terabytes.")) |
3009 | - |
3010 | class Meta: |
3011 | - model = Node |
3012 | + model = Machine |
3013 | |
3014 | - # Fields that the form should generate automatically from the |
3015 | - # model: |
3016 | - # Note: fields have to be added here even if they were defined manually |
3017 | - # elsewhere in the form |
3018 | - fields = ( |
3019 | - 'hostname', |
3020 | + fields = NodeForm.Meta.fields + ( |
3021 | 'architecture', |
3022 | 'osystem', |
3023 | 'distro_series', |
3024 | 'license_key', |
3025 | - 'disable_ipv4', |
3026 | - 'swap_size', |
3027 | 'min_hwe_kernel', |
3028 | - 'hwe_kernel' |
3029 | - ) |
3030 | - |
3031 | - |
3032 | -class DeviceForm(MAASModelForm): |
3033 | + 'hwe_kernel', |
3034 | + ) |
3035 | + |
3036 | + |
3037 | +class DeviceForm(NodeForm): |
3038 | parent = forms.ModelChoiceField( |
3039 | required=False, initial=None, |
3040 | queryset=Node.objects.all(), to_field_name='system_id') |
3041 | @@ -627,8 +659,7 @@ |
3042 | class Meta: |
3043 | model = Device |
3044 | |
3045 | - fields = ( |
3046 | - 'hostname', |
3047 | + fields = NodeForm.Meta.fields + ( |
3048 | 'parent', |
3049 | ) |
3050 | |
3051 | @@ -637,8 +668,6 @@ |
3052 | self.request = request |
3053 | |
3054 | instance = kwargs.get('instance') |
3055 | - # Are we creating a new device object? |
3056 | - self.new_device = (instance is None) |
3057 | self.set_up_initial_device(instance) |
3058 | |
3059 | def set_up_initial_device(self, instance): |
3060 | @@ -652,12 +681,13 @@ |
3061 | def save(self, commit=True): |
3062 | device = super(DeviceForm, self).save(commit=False) |
3063 | device.node_type = NODE_TYPE.DEVICE |
3064 | - if self.new_device: |
3065 | + if self.new_node: |
3066 | # Set the owner: devices are owned by their creator. |
3067 | device.owner = self.request.user |
3068 | device.save() |
3069 | return device |
3070 | |
3071 | + |
3072 | CLUSTER_NOT_AVAILABLE = mark_safe( |
3073 | "The cluster controller for this node is not responding; power type " |
3074 | "validation is not available. " |
3075 | @@ -673,7 +703,6 @@ |
3076 | |
3077 | class AdminNodeForm(NodeForm): |
3078 | """A `NodeForm` which includes fields that only an admin may change.""" |
3079 | - |
3080 | zone = forms.ModelChoiceField( |
3081 | label="Physical zone", required=False, |
3082 | initial=Zone.objects.get_default_zone, |
3083 | @@ -699,7 +728,6 @@ |
3084 | data=data, instance=instance, **kwargs) |
3085 | self.request = request |
3086 | self.set_up_initial_zone(instance) |
3087 | - AdminNodeForm.set_up_power_type(self, data, instance) |
3088 | # The zone field is not required because we want to be able |
3089 | # to omit it when using that form in the API. |
3090 | # We don't want the UI to show an entry for the 'empty' zone, |
3091 | @@ -718,17 +746,47 @@ |
3092 | if instance is not None: |
3093 | self.initial['zone'] = instance.zone.name |
3094 | |
3095 | + def save(self, *args, **kwargs): |
3096 | + """Persist the node into the database.""" |
3097 | + node = super(AdminNodeForm, self).save(commit=False) |
3098 | + zone = self.cleaned_data.get('zone') |
3099 | + if zone: |
3100 | + node.zone = zone |
3101 | + if kwargs.get('commit', True): |
3102 | + node.save(*args, **kwargs) |
3103 | + self.save_m2m() # Save many to many relations. |
3104 | + return node |
3105 | + |
3106 | + |
3107 | +class AdminMachineForm(MachineForm, AdminNodeForm): |
3108 | + """A `MachineForm` which includes fields that only an admin may change.""" |
3109 | + |
3110 | + class Meta: |
3111 | + model = Machine |
3112 | + |
3113 | + # Fields that the form should generate automatically from the |
3114 | + # model: |
3115 | + fields = MachineForm.Meta.fields + ( |
3116 | + 'cpu_count', |
3117 | + 'memory', |
3118 | + ) |
3119 | + |
3120 | + def __init__(self, data=None, instance=None, request=None, **kwargs): |
3121 | + super(AdminMachineForm, self).__init__( |
3122 | + data=data, instance=instance, **kwargs) |
3123 | + AdminMachineForm.set_up_power_type(self, data, instance) |
3124 | + |
3125 | @staticmethod |
3126 | - def _get_power_type(form, data, node): |
3127 | + def _get_power_type(form, data, machine): |
3128 | if data is None: |
3129 | data = {} |
3130 | |
3131 | power_type = data.get('power_type', form.initial.get('power_type')) |
3132 | |
3133 | - # If power_type is None (this is a node creation form or this |
3134 | + # If power_type is None (this is a machine creation form or this |
3135 | # form deals with an API call which does not change the value of |
3136 | - # 'power_type') or invalid: get the node's current 'power_type' |
3137 | - # value or the default value if this form is not linked to a node. |
3138 | + # 'power_type') or invalid: get the machine's current 'power_type' |
3139 | + # value or the default value if this form is not linked to a machine. |
3140 | try: |
3141 | power_types = get_power_types() |
3142 | except ClusterUnavailable as e: |
3143 | @@ -741,17 +799,17 @@ |
3144 | return '' |
3145 | |
3146 | if power_type not in power_types: |
3147 | - return '' if node is None else node.power_type |
3148 | + return '' if machine is None else machine.power_type |
3149 | return power_type |
3150 | |
3151 | @staticmethod |
3152 | - def set_up_power_type(form, data, node=None): |
3153 | + def set_up_power_type(form, data, machine=None): |
3154 | """Set up the 'power_type' and 'power_parameters' fields. |
3155 | |
3156 | This can't be done at the model level because the choices need to |
3157 | be generated on the fly by get_power_type_choices(). |
3158 | """ |
3159 | - power_type = AdminNodeForm._get_power_type(form, data, node) |
3160 | + power_type = AdminMachineForm._get_power_type(form, data, machine) |
3161 | choices = [BLANK_CHOICE] + get_power_type_choices() |
3162 | form.fields['power_type'] = forms.ChoiceField( |
3163 | required=False, choices=choices, initial=power_type) |
3164 | @@ -782,30 +840,37 @@ |
3165 | return cleaned_data |
3166 | |
3167 | def clean(self): |
3168 | - cleaned_data = super(AdminNodeForm, self).clean() |
3169 | - return AdminNodeForm.check_power_type(self, cleaned_data) |
3170 | + cleaned_data = super(AdminMachineForm, self).clean() |
3171 | + return AdminMachineForm.check_power_type(self, cleaned_data) |
3172 | |
3173 | @staticmethod |
3174 | - def set_power_type(form, node): |
3175 | - """Persist the node into the database.""" |
3176 | + def set_power_type(form, machine): |
3177 | + """Persist the machine into the database.""" |
3178 | power_type = form.cleaned_data.get('power_type') |
3179 | if power_type is not None: |
3180 | - node.power_type = power_type |
3181 | + machine.power_type = power_type |
3182 | power_parameters = form.cleaned_data.get('power_parameters') |
3183 | if power_parameters is not None: |
3184 | - node.power_parameters = power_parameters |
3185 | + machine.power_parameters = power_parameters |
3186 | |
3187 | def save(self, *args, **kwargs): |
3188 | """Persist the node into the database.""" |
3189 | - node = super(AdminNodeForm, self).save(commit=False) |
3190 | + machine = super(AdminMachineForm, self).save(commit=False) |
3191 | zone = self.cleaned_data.get('zone') |
3192 | if zone: |
3193 | - node.zone = zone |
3194 | - AdminNodeForm.set_power_type(self, node) |
3195 | + machine.zone = zone |
3196 | + AdminMachineForm.set_power_type(self, machine) |
3197 | if kwargs.get('commit', True): |
3198 | - node.save(*args, **kwargs) |
3199 | + machine.save(*args, **kwargs) |
3200 | self.save_m2m() # Save many to many relations. |
3201 | - return node |
3202 | + return machine |
3203 | + |
3204 | + |
3205 | +def get_machine_edit_form(user): |
3206 | + if user.is_superuser: |
3207 | + return AdminMachineForm |
3208 | + else: |
3209 | + return MachineForm |
3210 | |
3211 | |
3212 | def get_node_edit_form(user): |
3213 | @@ -1008,40 +1073,41 @@ |
3214 | """A form mixin which dynamically adds power_type and power_parameters to |
3215 | the list of fields. This mixin also overrides the 'save' method to persist |
3216 | these fields and is intended to be used with a class inheriting from |
3217 | - NodeForm. |
3218 | + MachineForm. |
3219 | """ |
3220 | |
3221 | def __init__(self, *args, **kwargs): |
3222 | super(WithPowerMixin, self).__init__(*args, **kwargs) |
3223 | self.data = self.data.copy() |
3224 | - AdminNodeForm.set_up_power_type(self, self.data) |
3225 | + AdminMachineForm.set_up_power_type(self, self.data) |
3226 | |
3227 | def clean(self): |
3228 | cleaned_data = super(WithPowerMixin, self).clean() |
3229 | - return AdminNodeForm.check_power_type(self, cleaned_data) |
3230 | + return AdminMachineForm.check_power_type(self, cleaned_data) |
3231 | |
3232 | def save(self, *args, **kwargs): |
3233 | """Persist the node into the database.""" |
3234 | node = super(WithPowerMixin, self).save() |
3235 | - AdminNodeForm.set_power_type(self, node) |
3236 | + AdminMachineForm.set_power_type(self, node) |
3237 | node.save() |
3238 | return node |
3239 | |
3240 | |
3241 | -class AdminNodeWithMACAddressesForm(WithMACAddressesMixin, AdminNodeForm): |
3242 | - """A version of the AdminNodeForm which includes the multi-MAC address |
3243 | +class AdminMachineWithMACAddressesForm( |
3244 | + WithMACAddressesMixin, AdminMachineForm): |
3245 | + """A version of the AdminMachineForm which includes the multi-MAC address |
3246 | field. |
3247 | """ |
3248 | |
3249 | |
3250 | -class NodeWithMACAddressesForm(WithMACAddressesMixin, NodeForm): |
3251 | - """A version of the NodeForm which includes the multi-MAC address field. |
3252 | +class MachineWithMACAddressesForm(WithMACAddressesMixin, MachineForm): |
3253 | + """A version of the MachineForm which includes the multi-MAC address field. |
3254 | """ |
3255 | |
3256 | |
3257 | -class NodeWithPowerAndMACAddressesForm( |
3258 | - WithPowerMixin, NodeWithMACAddressesForm): |
3259 | - """A version of the NodeForm which includes the power fields. |
3260 | +class MachineWithPowerAndMACAddressesForm( |
3261 | + WithPowerMixin, MachineWithMACAddressesForm): |
3262 | + """A version of the MachineForm which includes the power fields. |
3263 | """ |
3264 | |
3265 | |
3266 | @@ -1050,11 +1116,11 @@ |
3267 | """ |
3268 | |
3269 | |
3270 | -def get_node_create_form(user): |
3271 | +def get_machine_create_form(user): |
3272 | if user.is_superuser: |
3273 | - return AdminNodeWithMACAddressesForm |
3274 | + return AdminMachineWithMACAddressesForm |
3275 | else: |
3276 | - return NodeWithPowerAndMACAddressesForm |
3277 | + return MachineWithPowerAndMACAddressesForm |
3278 | |
3279 | |
3280 | class ProfileForm(MAASModelForm): |
3281 | |
3282 | === modified file 'src/maasserver/models/node.py' |
3283 | --- src/maasserver/models/node.py 2016-03-02 14:21:29 +0000 |
3284 | +++ src/maasserver/models/node.py 2016-03-02 18:21:51 +0000 |
3285 | @@ -3361,8 +3361,8 @@ |
3286 | |
3287 | def add_chassis( |
3288 | self, user, chassis_type, hostname, username=None, password=None, |
3289 | - accept_all=False, prefix_filter=None, power_control=None, |
3290 | - port=None, protocol=None): |
3291 | + accept_all=False, domain=None, prefix_filter=None, |
3292 | + power_control=None, port=None, protocol=None): |
3293 | self._register_request_event( |
3294 | self.owner, |
3295 | EVENT_TYPES.REQUEST_RACK_CONTROLLER_ADD_CHASSIS, |
3296 | @@ -3371,8 +3371,9 @@ |
3297 | call = client( |
3298 | AddChassis, user=user, chassis_type=chassis_type, |
3299 | hostname=hostname, username=username, password=password, |
3300 | - accept_all=accept_all, prefix_filter=prefix_filter, |
3301 | - power_control=power_control, port=port, protocol=protocol) |
3302 | + accept_all=accept_all, domain=domain, |
3303 | + prefix_filter=prefix_filter, power_control=power_control, |
3304 | + port=port, protocol=protocol) |
3305 | call.wait(30) |
3306 | |
3307 | def get_bmc_accessible_nodes(self): |
3308 | |
3309 | === modified file 'src/maasserver/models/tests/test_node.py' |
3310 | --- src/maasserver/models/tests/test_node.py 2016-03-02 17:21:09 +0000 |
3311 | +++ src/maasserver/models/tests/test_node.py 2016-03-02 18:21:51 +0000 |
3312 | @@ -5650,6 +5650,7 @@ |
3313 | username = factory.make_name('username') |
3314 | password = factory.make_name('password') |
3315 | accept_all = factory.pick_bool() |
3316 | + domain = factory.make_name('domain') |
3317 | prefix_filter = factory.make_name('prefix_filter') |
3318 | power_control = factory.make_name('power_control') |
3319 | port = random.randint(0, 65535) |
3320 | @@ -5657,15 +5658,16 @@ |
3321 | |
3322 | rackcontroller.add_chassis( |
3323 | user, chassis_type, hostname, username, password, accept_all, |
3324 | - prefix_filter, power_control, port, given_protocol) |
3325 | + domain, prefix_filter, power_control, port, given_protocol) |
3326 | |
3327 | self.expectThat( |
3328 | protocol.AddChassis, |
3329 | MockCalledOnceWith( |
3330 | ANY, user=user, chassis_type=chassis_type, hostname=hostname, |
3331 | username=username, password=password, accept_all=accept_all, |
3332 | - prefix_filter=prefix_filter, power_control=power_control, |
3333 | - port=port, protocol=given_protocol)) |
3334 | + domain=domain, prefix_filter=prefix_filter, |
3335 | + power_control=power_control, port=port, |
3336 | + protocol=given_protocol)) |
3337 | |
3338 | def test_add_chassis_logs_user_request(self): |
3339 | rackcontroller = factory.make_RackController() |
3340 | @@ -5682,6 +5684,7 @@ |
3341 | username = factory.make_name('username') |
3342 | password = factory.make_name('password') |
3343 | accept_all = factory.pick_bool() |
3344 | + domain = factory.make_name('domain') |
3345 | prefix_filter = factory.make_name('prefix_filter') |
3346 | power_control = factory.make_name('power_control') |
3347 | port = random.randint(0, 65535) |
3348 | @@ -5690,7 +5693,7 @@ |
3349 | register_event = self.patch(rackcontroller, '_register_request_event') |
3350 | rackcontroller.add_chassis( |
3351 | user, chassis_type, hostname, username, password, accept_all, |
3352 | - prefix_filter, power_control, port, given_protocol) |
3353 | + domain, prefix_filter, power_control, port, given_protocol) |
3354 | post_commit_hooks.reset() # Ignore these for now. |
3355 | self.assertThat(register_event, MockCalledOnceWith( |
3356 | rackcontroller.owner, |
3357 | |
3358 | === modified file 'src/maasserver/preseed.py' |
3359 | --- src/maasserver/preseed.py 2016-02-26 18:34:28 +0000 |
3360 | +++ src/maasserver/preseed.py 2016-03-02 18:21:51 +0000 |
3361 | @@ -601,7 +601,7 @@ |
3362 | 'osystem': osystem, |
3363 | 'release': release, |
3364 | 'server_host': server_host, |
3365 | - 'server_url': absolute_reverse('nodes_handler', base_url=base_url), |
3366 | + 'server_url': absolute_reverse('machines_handler', base_url=base_url), |
3367 | 'metadata_enlist_url': absolute_reverse('enlist', base_url=base_url), |
3368 | 'enable_http_proxy': Config.objects.get_config('enable_http_proxy'), |
3369 | 'http_proxy': Config.objects.get_config('http_proxy'), |
3370 | |
3371 | === modified file 'src/maasserver/rpc/nodes.py' |
3372 | --- src/maasserver/rpc/nodes.py 2016-02-11 15:06:36 +0000 |
3373 | +++ src/maasserver/rpc/nodes.py 2016-03-02 18:21:51 +0000 |
3374 | @@ -19,7 +19,7 @@ |
3375 | from maasserver import exceptions |
3376 | from maasserver.api.utils import get_overridden_query_dict |
3377 | from maasserver.enum import NODE_STATUS |
3378 | -from maasserver.forms import AdminNodeWithMACAddressesForm |
3379 | +from maasserver.forms import AdminMachineWithMACAddressesForm |
3380 | from maasserver.models import ( |
3381 | Node, |
3382 | PhysicalInterface, |
3383 | @@ -177,8 +177,9 @@ |
3384 | |
3385 | @synchronous |
3386 | @transactional |
3387 | -def create_node(architecture, power_type, |
3388 | - power_parameters, mac_addresses, hostname=None): |
3389 | +def create_node( |
3390 | + architecture, power_type, power_parameters, mac_addresses, domain=None, |
3391 | + hostname=None): |
3392 | """Create a new `Node` and return it. |
3393 | |
3394 | :param architecture: The architecture of the new node. |
3395 | @@ -187,6 +188,7 @@ |
3396 | for the new node. |
3397 | :param mac_addresses: An iterable of MAC addresses that belong to |
3398 | the node. |
3399 | + :param domain: The domain the node should join. |
3400 | :param hostname: the desired hostname for the new node |
3401 | """ |
3402 | # Check that there isn't already a node with one of our MAC |
3403 | @@ -209,12 +211,15 @@ |
3404 | 'mac_addresses': mac_addresses, |
3405 | } |
3406 | |
3407 | + if domain is not None: |
3408 | + data['domain'] = domain |
3409 | + |
3410 | if hostname is not None: |
3411 | data['hostname'] = hostname.strip() |
3412 | |
3413 | data_query_dict = get_overridden_query_dict( |
3414 | - {}, data, AdminNodeWithMACAddressesForm.Meta.fields) |
3415 | - form = AdminNodeWithMACAddressesForm(data_query_dict) |
3416 | + {}, data, AdminMachineWithMACAddressesForm.Meta.fields) |
3417 | + form = AdminMachineWithMACAddressesForm(data_query_dict) |
3418 | if form.is_valid(): |
3419 | node = form.save() |
3420 | # We have to explicitly save the power parameters; the form |
3421 | |
3422 | === modified file 'src/maasserver/rpc/regionservice.py' |
3423 | --- src/maasserver/rpc/regionservice.py 2016-02-26 16:19:41 +0000 |
3424 | +++ src/maasserver/rpc/regionservice.py 2016-03-02 18:21:51 +0000 |
3425 | @@ -349,7 +349,7 @@ |
3426 | |
3427 | @region.CreateNode.responder |
3428 | def create_node(self, architecture, power_type, power_parameters, |
3429 | - mac_addresses, hostname=None): |
3430 | + mac_addresses, domain=None, hostname=None): |
3431 | """create_node() |
3432 | |
3433 | Implementation of |
3434 | @@ -357,7 +357,7 @@ |
3435 | """ |
3436 | d = deferToDatabase( |
3437 | create_node, architecture, power_type, power_parameters, |
3438 | - mac_addresses, hostname=hostname) |
3439 | + mac_addresses, domain=domain, hostname=hostname) |
3440 | d.addCallback(lambda node: {'system_id': node.system_id}) |
3441 | return d |
3442 | |
3443 | |
3444 | === modified file 'src/maasserver/rpc/tests/test_nodes.py' |
3445 | --- src/maasserver/rpc/tests/test_nodes.py 2016-02-26 18:48:26 +0000 |
3446 | +++ src/maasserver/rpc/tests/test_nodes.py 2016-03-02 18:21:51 +0000 |
3447 | @@ -153,6 +153,55 @@ |
3448 | architecture, power_type, power_parameters, |
3449 | mac_addresses, hostname=hostname) |
3450 | |
3451 | + def test__creates_node_with_explicit_domain(self): |
3452 | + self.prepare_rack_rpc() |
3453 | + |
3454 | + mac_addresses = [ |
3455 | + factory.make_mac_address() for _ in range(3)] |
3456 | + architecture = make_usable_architecture(self) |
3457 | + hostname = factory.make_hostname() |
3458 | + domain = factory.make_Domain() |
3459 | + power_type = random.choice(self.power_types)['name'] |
3460 | + power_parameters = dumps({}) |
3461 | + |
3462 | + node = create_node( |
3463 | + architecture, power_type, power_parameters, |
3464 | + mac_addresses, domain=domain.name, hostname=hostname) |
3465 | + |
3466 | + self.assertEqual( |
3467 | + ( |
3468 | + architecture, |
3469 | + power_type, |
3470 | + {}, |
3471 | + domain.id, |
3472 | + hostname, |
3473 | + ), |
3474 | + ( |
3475 | + node.architecture, |
3476 | + node.power_type, |
3477 | + node.power_parameters, |
3478 | + node.domain.id, |
3479 | + node.hostname, |
3480 | + )) |
3481 | + self.expectThat(node.id, Not(Is(None))) |
3482 | + self.assertItemsEqual( |
3483 | + mac_addresses, |
3484 | + [nic.mac_address for nic in node.interface_set.all()]) |
3485 | + |
3486 | + def test__create_node_fails_with_invalid_domain(self): |
3487 | + self.prepare_rack_rpc() |
3488 | + |
3489 | + mac_addresses = [ |
3490 | + factory.make_mac_address() for _ in range(3)] |
3491 | + architecture = make_usable_architecture(self) |
3492 | + power_type = random.choice(self.power_types)['name'] |
3493 | + power_parameters = dumps({}) |
3494 | + |
3495 | + with ExpectedException(ValidationError): |
3496 | + create_node( |
3497 | + architecture, power_type, power_parameters, |
3498 | + mac_addresses, factory.make_name('domain')) |
3499 | + |
3500 | def test__raises_validation_errors_for_invalid_data(self): |
3501 | self.prepare_rack_rpc() |
3502 | |
3503 | |
3504 | === modified file 'src/maasserver/rpc/tests/test_regionservice.py' |
3505 | --- src/maasserver/rpc/tests/test_regionservice.py 2016-02-27 02:32:00 +0000 |
3506 | +++ src/maasserver/rpc/tests/test_regionservice.py 2016-03-02 18:21:51 +0000 |
3507 | @@ -2416,9 +2416,10 @@ |
3508 | |
3509 | params = { |
3510 | 'architecture': make_usable_architecture(self), |
3511 | - 'power_type': factory.make_name("power_type"), |
3512 | + 'power_type': factory.make_name('power_type'), |
3513 | 'power_parameters': dumps({}), |
3514 | 'mac_addresses': [factory.make_mac_address()], |
3515 | + 'domain': factory.make_name('domain'), |
3516 | 'hostname': None, |
3517 | } |
3518 | |
3519 | @@ -2431,6 +2432,7 @@ |
3520 | MockCalledOnceWith( |
3521 | params['architecture'], params['power_type'], |
3522 | params['power_parameters'], params['mac_addresses'], |
3523 | + domain=params['domain'], |
3524 | hostname=params['hostname'])) |
3525 | self.assertEqual( |
3526 | create_node_function.return_value.system_id, |
3527 | |
3528 | === modified file 'src/maasserver/tests/test_forms_device.py' |
3529 | --- src/maasserver/tests/test_forms_device.py 2016-02-26 18:39:26 +0000 |
3530 | +++ src/maasserver/tests/test_forms_device.py 2016-03-02 18:21:51 +0000 |
3531 | @@ -1,4 +1,4 @@ |
3532 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
3533 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
3534 | # GNU Affero General Public License version 3 (see the file LICENSE). |
3535 | |
3536 | """Tests for device forms.""" |
3537 | @@ -16,26 +16,15 @@ |
3538 | def test_contains_limited_set_of_fields(self): |
3539 | form = DeviceForm() |
3540 | |
3541 | - self.assertEqual( |
3542 | + self.assertItemsEqual( |
3543 | [ |
3544 | 'hostname', |
3545 | + 'domain', |
3546 | 'parent', |
3547 | + 'disable_ipv4', |
3548 | + 'swap_size', |
3549 | ], list(form.fields)) |
3550 | |
3551 | - def test_changes_device_hostname(self): |
3552 | - device = factory.make_Device() |
3553 | - hostname = factory.make_string() |
3554 | - |
3555 | - form = DeviceForm( |
3556 | - data={ |
3557 | - 'hostname': hostname, |
3558 | - }, |
3559 | - instance=device) |
3560 | - form.save() |
3561 | - reload_object(device) |
3562 | - |
3563 | - self.assertEqual(hostname, device.hostname) |
3564 | - |
3565 | def test_changes_device_parent(self): |
3566 | device = factory.make_Device() |
3567 | parent = factory.make_Node() |
3568 | |
3569 | === modified file 'src/maasserver/tests/test_forms_helpers.py' |
3570 | --- src/maasserver/tests/test_forms_helpers.py 2016-02-02 14:20:45 +0000 |
3571 | +++ src/maasserver/tests/test_forms_helpers.py 2016-03-02 18:21:51 +0000 |
3572 | @@ -8,14 +8,17 @@ |
3573 | from django.forms import CharField |
3574 | from maasserver.enum import BOOT_RESOURCE_TYPE |
3575 | from maasserver.forms import ( |
3576 | + AdminMachineForm, |
3577 | + AdminMachineWithMACAddressesForm, |
3578 | AdminNodeForm, |
3579 | - AdminNodeWithMACAddressesForm, |
3580 | - get_node_create_form, |
3581 | + get_machine_create_form, |
3582 | + get_machine_edit_form, |
3583 | get_node_edit_form, |
3584 | list_all_usable_architectures, |
3585 | MAASModelForm, |
3586 | + MachineForm, |
3587 | + MachineWithPowerAndMACAddressesForm, |
3588 | NodeForm, |
3589 | - NodeWithPowerAndMACAddressesForm, |
3590 | pick_default_architecture, |
3591 | remove_None_values, |
3592 | ) |
3593 | @@ -98,23 +101,31 @@ |
3594 | def test_remove_None_values_leaves_empty_dict_untouched(self): |
3595 | self.assertEqual({}, remove_None_values({})) |
3596 | |
3597 | + def test_get_machine_edit_form_returns_MachineForm_if_non_admin(self): |
3598 | + user = factory.make_User() |
3599 | + self.assertEqual(MachineForm, get_machine_edit_form(user)) |
3600 | + |
3601 | + def test_get_machine_edit_form_returns_AdminMachineForm_if_admin(self): |
3602 | + admin = factory.make_admin() |
3603 | + self.assertEqual(AdminMachineForm, get_machine_edit_form(admin)) |
3604 | + |
3605 | def test_get_node_edit_form_returns_NodeForm_if_non_admin(self): |
3606 | user = factory.make_User() |
3607 | self.assertEqual(NodeForm, get_node_edit_form(user)) |
3608 | |
3609 | - def test_get_node_edit_form_returns_APIAdminNodeEdit_if_admin(self): |
3610 | + def test_get_node_edit_form_returns_AdminNodeForm_if_admin(self): |
3611 | admin = factory.make_admin() |
3612 | self.assertEqual(AdminNodeForm, get_node_edit_form(admin)) |
3613 | |
3614 | - def test_get_node_create_form_if_non_admin(self): |
3615 | + def test_get_machine_create_form_if_non_admin(self): |
3616 | user = factory.make_User() |
3617 | self.assertEqual( |
3618 | - NodeWithPowerAndMACAddressesForm, get_node_create_form(user)) |
3619 | + MachineWithPowerAndMACAddressesForm, get_machine_create_form(user)) |
3620 | |
3621 | - def test_get_node_create_form_if_admin(self): |
3622 | + def test_get_machine_create_form_if_admin(self): |
3623 | admin = factory.make_admin() |
3624 | self.assertEqual( |
3625 | - AdminNodeWithMACAddressesForm, get_node_create_form(admin)) |
3626 | + AdminMachineWithMACAddressesForm, get_machine_create_form(admin)) |
3627 | |
3628 | |
3629 | class TestMAASModelForm(MAASTransactionServerTestCase): |
3630 | |
3631 | === renamed file 'src/maasserver/tests/test_forms_node.py' => 'src/maasserver/tests/test_forms_machine.py' |
3632 | --- src/maasserver/tests/test_forms_node.py 2016-03-01 19:02:08 +0000 |
3633 | +++ src/maasserver/tests/test_forms_machine.py 2016-03-02 18:21:51 +0000 |
3634 | @@ -1,4 +1,4 @@ |
3635 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
3636 | +# Copyright 2016 Canonical Ltd. This software is licensed under the |
3637 | # GNU Affero General Public License version 3 (see the file LICENSE). |
3638 | |
3639 | """Tests for node forms.""" |
3640 | @@ -6,7 +6,6 @@ |
3641 | __all__ = [] |
3642 | |
3643 | from crochet import TimeoutError |
3644 | -from django.core.exceptions import ValidationError |
3645 | from maasserver import forms |
3646 | from maasserver.clusterrpc.power_parameters import get_power_type_choices |
3647 | from maasserver.clusterrpc.testing.osystems import ( |
3648 | @@ -14,13 +13,11 @@ |
3649 | make_rpc_release, |
3650 | ) |
3651 | from maasserver.forms import ( |
3652 | - AdminNodeForm, |
3653 | + AdminMachineForm, |
3654 | BLANK_CHOICE, |
3655 | - NodeChoiceField, |
3656 | - NodeForm, |
3657 | + MachineForm, |
3658 | pick_default_architecture, |
3659 | ) |
3660 | -from maasserver.models import Node |
3661 | from maasserver.testing.architecture import ( |
3662 | make_usable_architecture, |
3663 | patch_usable_architectures, |
3664 | @@ -32,21 +29,21 @@ |
3665 | patch_usable_osystems, |
3666 | ) |
3667 | from maasserver.testing.testcase import MAASServerTestCase |
3668 | -from maasserver.utils.orm import reload_object |
3669 | from provisioningserver.rpc.exceptions import ( |
3670 | NoConnectionsAvailable, |
3671 | NoSuchOperatingSystem, |
3672 | ) |
3673 | |
3674 | |
3675 | -class TestNodeForm(MAASServerTestCase): |
3676 | +class TestMachineForm(MAASServerTestCase): |
3677 | |
3678 | def test_contains_limited_set_of_fields(self): |
3679 | - form = NodeForm() |
3680 | + form = MachineForm() |
3681 | |
3682 | - self.assertEqual( |
3683 | + self.assertItemsEqual( |
3684 | [ |
3685 | 'hostname', |
3686 | + 'domain', |
3687 | 'architecture', |
3688 | 'osystem', |
3689 | 'distro_series', |
3690 | @@ -57,24 +54,9 @@ |
3691 | 'hwe_kernel', |
3692 | ], list(form.fields)) |
3693 | |
3694 | - def test_changes_node(self): |
3695 | - node = factory.make_Node() |
3696 | - hostname = factory.make_string() |
3697 | - patch_usable_architectures(self, [node.architecture]) |
3698 | - |
3699 | - form = NodeForm( |
3700 | - data={ |
3701 | - 'hostname': hostname, |
3702 | - 'architecture': make_usable_architecture(self), |
3703 | - }, |
3704 | - instance=node) |
3705 | - form.save() |
3706 | - |
3707 | - self.assertEqual(hostname, node.hostname) |
3708 | - |
3709 | def test_accepts_usable_architecture(self): |
3710 | arch = make_usable_architecture(self) |
3711 | - form = NodeForm(data={ |
3712 | + form = MachineForm(data={ |
3713 | 'hostname': factory.make_name('host'), |
3714 | 'architecture': arch, |
3715 | }) |
3716 | @@ -82,7 +64,7 @@ |
3717 | |
3718 | def test_rejects_unusable_architecture(self): |
3719 | patch_usable_architectures(self) |
3720 | - form = NodeForm(data={ |
3721 | + form = MachineForm(data={ |
3722 | 'hostname': factory.make_name('host'), |
3723 | 'architecture': factory.make_name('arch'), |
3724 | }) |
3725 | @@ -92,7 +74,7 @@ |
3726 | def test_starts_with_default_architecture(self): |
3727 | arches = sorted([factory.make_name('arch') for _ in range(5)]) |
3728 | patch_usable_architectures(self, arches) |
3729 | - form = NodeForm() |
3730 | + form = MachineForm() |
3731 | self.assertEqual( |
3732 | pick_default_architecture(arches), |
3733 | form.fields['architecture'].initial) |
3734 | @@ -102,7 +84,7 @@ |
3735 | node = factory.make_Node( |
3736 | owner=self.logged_in_user) |
3737 | osystem = make_usable_osystem(self) |
3738 | - form = NodeForm(data={ |
3739 | + form = MachineForm(data={ |
3740 | 'hostname': factory.make_name('host'), |
3741 | 'architecture': make_usable_architecture(self), |
3742 | 'osystem': osystem['name'], |
3743 | @@ -114,12 +96,12 @@ |
3744 | |
3745 | def test_form_validates_min_hwe_kernel_by_passing_invalid_config(self): |
3746 | node = factory.make_Node(min_hwe_kernel='hwe-t') |
3747 | - form = NodeForm(instance=node) |
3748 | + form = MachineForm(instance=node) |
3749 | self.assertEqual(form.is_valid(), False) |
3750 | |
3751 | def test_adds_blank_default_when_no_arches_available(self): |
3752 | patch_usable_architectures(self, []) |
3753 | - form = NodeForm() |
3754 | + form = MachineForm() |
3755 | self.assertEqual( |
3756 | [BLANK_CHOICE], |
3757 | form.fields['architecture'].choices) |
3758 | @@ -128,7 +110,7 @@ |
3759 | self.client_log_in() |
3760 | node = factory.make_Node(owner=self.logged_in_user) |
3761 | osystem = make_usable_osystem(self) |
3762 | - form = NodeForm(data={ |
3763 | + form = MachineForm(data={ |
3764 | 'hostname': factory.make_name('host'), |
3765 | 'architecture': make_usable_architecture(self), |
3766 | 'osystem': osystem['name'], |
3767 | @@ -140,7 +122,7 @@ |
3768 | self.client_log_in() |
3769 | node = factory.make_Node(owner=self.logged_in_user) |
3770 | patch_usable_osystems(self) |
3771 | - form = NodeForm(data={ |
3772 | + form = MachineForm(data={ |
3773 | 'hostname': factory.make_name('host'), |
3774 | 'architecture': make_usable_architecture(self), |
3775 | 'osystem': factory.make_name('os'), |
3776 | @@ -154,7 +136,7 @@ |
3777 | node = factory.make_Node(owner=self.logged_in_user) |
3778 | osystems = [make_osystem_with_releases(self) for _ in range(5)] |
3779 | patch_usable_osystems(self, osystems) |
3780 | - form = NodeForm(instance=node) |
3781 | + form = MachineForm(instance=node) |
3782 | self.assertEqual( |
3783 | '', |
3784 | form.fields['osystem'].initial) |
3785 | @@ -164,7 +146,7 @@ |
3786 | node = factory.make_Node(owner=self.logged_in_user) |
3787 | osystem = make_usable_osystem(self) |
3788 | release = osystem['default_release'] |
3789 | - form = NodeForm(data={ |
3790 | + form = MachineForm(data={ |
3791 | 'hostname': factory.make_name('host'), |
3792 | 'architecture': make_usable_architecture(self), |
3793 | 'osystem': osystem['name'], |
3794 | @@ -178,7 +160,7 @@ |
3795 | node = factory.make_Node(owner=self.logged_in_user) |
3796 | osystem = make_usable_osystem(self) |
3797 | release = factory.make_name('release') |
3798 | - form = NodeForm(data={ |
3799 | + form = MachineForm(data={ |
3800 | 'hostname': factory.make_name('host'), |
3801 | 'architecture': make_usable_architecture(self), |
3802 | 'osystem': osystem['name'], |
3803 | @@ -194,7 +176,7 @@ |
3804 | release = factory.make_name('release') |
3805 | make_usable_osystem( |
3806 | self, releases=[release + '6', release + '0', release + '3']) |
3807 | - form = NodeForm(data={ |
3808 | + form = MachineForm(data={ |
3809 | 'hostname': factory.make_name('host'), |
3810 | 'architecture': make_usable_architecture(self), |
3811 | }, |
3812 | @@ -210,7 +192,7 @@ |
3813 | self, |
3814 | osystem_name='ubuntu', |
3815 | releases=['trusty']) |
3816 | - form = NodeForm(data={ |
3817 | + form = MachineForm(data={ |
3818 | 'hostname': factory.make_name('host'), |
3819 | 'architecture': make_usable_architecture(self), |
3820 | }, |
3821 | @@ -223,7 +205,7 @@ |
3822 | node = factory.make_Node(owner=self.logged_in_user) |
3823 | osystems = [make_osystem_with_releases(self) for _ in range(5)] |
3824 | patch_usable_osystems(self, osystems) |
3825 | - form = NodeForm(instance=node) |
3826 | + form = MachineForm(instance=node) |
3827 | self.assertEqual( |
3828 | '', |
3829 | form.fields['distro_series'].initial) |
3830 | @@ -234,7 +216,7 @@ |
3831 | osystem = make_usable_osystem(self) |
3832 | release = osystem['default_release'] |
3833 | invalid = factory.make_name('invalid_os') |
3834 | - form = NodeForm(data={ |
3835 | + form = MachineForm(data={ |
3836 | 'hostname': factory.make_name('host'), |
3837 | 'architecture': make_usable_architecture(self), |
3838 | 'osystem': osystem['name'], |
3839 | @@ -253,7 +235,7 @@ |
3840 | license_key = factory.make_name('key') |
3841 | mock_validate = self.patch(forms, 'validate_license_key') |
3842 | mock_validate.return_value = False |
3843 | - form = NodeForm(data={ |
3844 | + form = MachineForm(data={ |
3845 | 'hostname': factory.make_name('host'), |
3846 | 'architecture': make_usable_architecture(self), |
3847 | 'osystem': osystem['name'], |
3848 | @@ -273,7 +255,7 @@ |
3849 | license_key = factory.make_name('key') |
3850 | mock_validate_for = self.patch(forms, 'validate_license_key_for') |
3851 | mock_validate_for.return_value = False |
3852 | - form = NodeForm(data={ |
3853 | + form = MachineForm(data={ |
3854 | 'architecture': make_usable_architecture(self), |
3855 | 'osystem': osystem['name'], |
3856 | 'distro_series': '%s/%s*' % (osystem['name'], release['name']), |
3857 | @@ -292,7 +274,7 @@ |
3858 | license_key = factory.make_name('key') |
3859 | mock_validate_for = self.patch(forms, 'validate_license_key_for') |
3860 | mock_validate_for.side_effect = NoConnectionsAvailable() |
3861 | - form = NodeForm(data={ |
3862 | + form = MachineForm(data={ |
3863 | 'architecture': make_usable_architecture(self), |
3864 | 'osystem': osystem['name'], |
3865 | 'distro_series': '%s/%s*' % (osystem['name'], release['name']), |
3866 | @@ -311,7 +293,7 @@ |
3867 | license_key = factory.make_name('key') |
3868 | mock_validate_for = self.patch(forms, 'validate_license_key_for') |
3869 | mock_validate_for.side_effect = TimeoutError() |
3870 | - form = NodeForm(data={ |
3871 | + form = MachineForm(data={ |
3872 | 'architecture': make_usable_architecture(self), |
3873 | 'osystem': osystem['name'], |
3874 | 'distro_series': '%s/%s*' % (osystem['name'], release['name']), |
3875 | @@ -330,7 +312,7 @@ |
3876 | license_key = factory.make_name('key') |
3877 | mock_validate_for = self.patch(forms, 'validate_license_key_for') |
3878 | mock_validate_for.side_effect = NoSuchOperatingSystem() |
3879 | - form = NodeForm(data={ |
3880 | + form = MachineForm(data={ |
3881 | 'architecture': make_usable_architecture(self), |
3882 | 'osystem': osystem['name'], |
3883 | 'distro_series': '%s/%s*' % (osystem['name'], release['name']), |
3884 | @@ -340,46 +322,18 @@ |
3885 | self.assertFalse(form.is_valid()) |
3886 | self.assertItemsEqual(['license_key'], form._errors.keys()) |
3887 | |
3888 | - def test_obeys_disable_ipv4_if_given(self): |
3889 | - setting = factory.pick_bool() |
3890 | - form = NodeForm( |
3891 | - data={ |
3892 | - 'architecture': make_usable_architecture(self), |
3893 | - 'disable_ipv4': setting, |
3894 | - }) |
3895 | - node = form.save() |
3896 | - self.assertEqual(setting, node.disable_ipv4) |
3897 | - |
3898 | - def test_takes_missing_disable_ipv4_as_False_in_UI(self): |
3899 | - form = NodeForm( |
3900 | - instance=factory.make_Node(disable_ipv4=True), |
3901 | - data={ |
3902 | - 'architecture': make_usable_architecture(self), |
3903 | - 'ui_submission': True, |
3904 | - }) |
3905 | - node = form.save() |
3906 | - self.assertFalse(node.disable_ipv4) |
3907 | - |
3908 | - def test_takes_missing_disable_ipv4_as_Unchanged_in_API(self): |
3909 | - form = NodeForm( |
3910 | - instance=factory.make_Node(disable_ipv4=True), |
3911 | - data={ |
3912 | - 'architecture': make_usable_architecture(self), |
3913 | - }) |
3914 | - node = form.save() |
3915 | - self.assertTrue(node.disable_ipv4) |
3916 | - |
3917 | - |
3918 | -class TestAdminNodeForm(MAASServerTestCase): |
3919 | - |
3920 | - def test_AdminNodeForm_contains_limited_set_of_fields(self): |
3921 | + |
3922 | +class TestAdminMachineForm(MAASServerTestCase): |
3923 | + |
3924 | + def test_AdminMachineForm_contains_limited_set_of_fields(self): |
3925 | self.client_log_in() |
3926 | node = factory.make_Node(owner=self.logged_in_user) |
3927 | - form = AdminNodeForm(instance=node) |
3928 | + form = AdminMachineForm(instance=node) |
3929 | |
3930 | - self.assertEqual( |
3931 | + self.assertItemsEqual( |
3932 | [ |
3933 | 'hostname', |
3934 | + 'domain', |
3935 | 'architecture', |
3936 | 'osystem', |
3937 | 'distro_series', |
3938 | @@ -396,59 +350,24 @@ |
3939 | ], |
3940 | list(form.fields)) |
3941 | |
3942 | - def test_AdminNodeForm_initialises_zone(self): |
3943 | - # The zone field uses "to_field_name", so that it can refer to a zone |
3944 | - # by name instead of by ID. A bug in Django breaks initialisation |
3945 | - # from an instance: the field tries to initialise the field using a |
3946 | - # zone's ID instead of its name, and ends up reverting to the default. |
3947 | - # The code must work around this bug. |
3948 | - zone = factory.make_Zone() |
3949 | - node = factory.make_Node(zone=zone) |
3950 | - # We'll create a form that makes a change, but not to the zone. |
3951 | - data = {'hostname': factory.make_name('host')} |
3952 | - form = AdminNodeForm(instance=node, data=data) |
3953 | - # The Django bug would stop the initial field value from being set, |
3954 | - # but the workaround ensures that it is initialised. |
3955 | - self.assertEqual(zone.name, form.initial['zone']) |
3956 | - |
3957 | - def test_AdminNodeForm_changes_node(self): |
3958 | - node = factory.make_Node() |
3959 | - zone = factory.make_Zone() |
3960 | - hostname = factory.make_string() |
3961 | - power_type = factory.pick_power_type() |
3962 | - form = AdminNodeForm( |
3963 | - data={ |
3964 | - 'hostname': hostname, |
3965 | - 'power_type': power_type, |
3966 | - 'architecture': make_usable_architecture(self), |
3967 | - 'zone': zone.name, |
3968 | - }, |
3969 | - instance=node) |
3970 | - form.save() |
3971 | - |
3972 | - node = reload_object(node) |
3973 | - self.assertEqual( |
3974 | - (node.hostname, node.power_type, node.zone), |
3975 | - (hostname, power_type, zone)) |
3976 | - |
3977 | - def test_AdminNodeForm_populates_power_type_choices(self): |
3978 | - form = AdminNodeForm() |
3979 | + def test_AdminMachineForm_populates_power_type_choices(self): |
3980 | + form = AdminMachineForm() |
3981 | self.assertEqual( |
3982 | [''] + [choice[0] for choice in get_power_type_choices()], |
3983 | [choice[0] for choice in form.fields['power_type'].choices]) |
3984 | |
3985 | - def test_AdminNodeForm_populates_power_type_initial(self): |
3986 | + def test_AdminMachineForm_populates_power_type_initial(self): |
3987 | node = factory.make_Node() |
3988 | - form = AdminNodeForm(instance=node) |
3989 | + form = AdminMachineForm(instance=node) |
3990 | self.assertEqual(node.power_type, form.fields['power_type'].initial) |
3991 | |
3992 | - def test_AdminNodeForm_changes_node_with_skip_check(self): |
3993 | + def test_AdminMachineForm_changes_node_with_skip_check(self): |
3994 | node = factory.make_Node() |
3995 | hostname = factory.make_string() |
3996 | power_type = factory.pick_power_type() |
3997 | power_parameters_field = factory.make_string() |
3998 | arch = make_usable_architecture(self) |
3999 | - form = AdminNodeForm( |
4000 | + form = AdminMachineForm( |
4001 | data={ |
4002 | 'hostname': hostname, |
4003 | 'architecture': arch, |
4004 | @@ -462,37 +381,3 @@ |
4005 | self.assertEqual( |
4006 | (hostname, power_type, {'field': power_parameters_field}), |
4007 | (node.hostname, node.power_type, node.power_parameters)) |
4008 | - |
4009 | - |
4010 | -class TestNodeChoiceField(MAASServerTestCase): |
4011 | - def test_allows_selecting_by_system_id(self): |
4012 | - node = factory.make_Node() |
4013 | - for _ in range(3): |
4014 | - factory.make_Node() |
4015 | - node_field = NodeChoiceField(Node.objects.filter()) |
4016 | - self.assertEqual(node, node_field.clean(node.system_id)) |
4017 | - |
4018 | - def test_allows_selecting_by_hostname(self): |
4019 | - node = factory.make_Node() |
4020 | - for _ in range(3): |
4021 | - factory.make_Node() |
4022 | - node_field = NodeChoiceField(Node.objects.filter()) |
4023 | - self.assertEqual(node, node_field.clean(node.hostname)) |
4024 | - |
4025 | - def test_raises_exception_when_not_found(self): |
4026 | - for _ in range(3): |
4027 | - factory.make_Node() |
4028 | - node_field = NodeChoiceField(Node.objects.filter()) |
4029 | - self.assertRaises( |
4030 | - ValidationError, node_field.clean, factory.make_name('query')) |
4031 | - |
4032 | - def test_works_with_multiple_entries_in_queryset(self): |
4033 | - # Regression test for lp:1551399 |
4034 | - vlan = factory.make_VLAN() |
4035 | - node = factory.make_Node_with_Interface_on_Subnet(vlan=vlan) |
4036 | - factory.make_Interface(node=node, vlan=vlan) |
4037 | - qs = Node.objects.filter_by_vids([vlan.vid]) |
4038 | - node_field = NodeChoiceField(qs) |
4039 | - # Double check that we have duplicated entires |
4040 | - self.assertEqual(2, len(qs.filter(system_id=node.system_id))) |
4041 | - self.assertEqual(node, node_field.clean(node.system_id)) |
4042 | |
4043 | === renamed file 'src/maasserver/tests/test_forms_nodewithmacaddresses.py' => 'src/maasserver/tests/test_forms_machinewithmacaddresses.py' |
4044 | --- src/maasserver/tests/test_forms_nodewithmacaddresses.py 2016-02-02 14:20:45 +0000 |
4045 | +++ src/maasserver/tests/test_forms_machinewithmacaddresses.py 2016-03-02 18:21:51 +0000 |
4046 | @@ -1,13 +1,13 @@ |
4047 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
4048 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
4049 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4050 | |
4051 | -"""Tests for `NodeWithMACAddressesForm`.""" |
4052 | +"""Tests for `MachineWithMACAddressesForm`.""" |
4053 | |
4054 | __all__ = [] |
4055 | |
4056 | from django.http import QueryDict |
4057 | from maasserver.enum import INTERFACE_TYPE |
4058 | -from maasserver.forms import NodeWithMACAddressesForm |
4059 | +from maasserver.forms import MachineWithMACAddressesForm |
4060 | from maasserver.testing.architecture import ( |
4061 | make_usable_architecture, |
4062 | patch_usable_architectures, |
4063 | @@ -17,7 +17,7 @@ |
4064 | from testtools.matchers import Contains |
4065 | |
4066 | |
4067 | -class NodeWithMACAddressesFormTest(MAASServerTestCase): |
4068 | +class MachineWithMACAddressesFormTest(MAASServerTestCase): |
4069 | |
4070 | def get_QueryDict(self, params): |
4071 | query_dict = QueryDict('', mutable=True) |
4072 | @@ -47,7 +47,7 @@ |
4073 | |
4074 | def test__valid(self): |
4075 | architecture = make_usable_architecture(self) |
4076 | - form = NodeWithMACAddressesForm( |
4077 | + form = MachineWithMACAddressesForm( |
4078 | data=self.make_params( |
4079 | mac_addresses=['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f'], |
4080 | architecture=architecture)) |
4081 | @@ -62,7 +62,7 @@ |
4082 | # If the form only has one (invalid) MAC address field to validate, |
4083 | # the error message in form.errors['mac_addresses'] is the |
4084 | # message from the field's validation error. |
4085 | - form = NodeWithMACAddressesForm( |
4086 | + form = MachineWithMACAddressesForm( |
4087 | data=self.make_params(mac_addresses=['invalid'])) |
4088 | |
4089 | self.assertFalse(form.is_valid()) |
4090 | @@ -75,7 +75,7 @@ |
4091 | # If the form has multiple MAC address fields to validate, |
4092 | # if one or more fields are invalid, a single error message is |
4093 | # present in form.errors['mac_addresses'] after validation. |
4094 | - form = NodeWithMACAddressesForm( |
4095 | + form = MachineWithMACAddressesForm( |
4096 | data=self.make_params(mac_addresses=['invalid_1', 'invalid_2'])) |
4097 | |
4098 | self.assertFalse(form.is_valid()) |
4099 | @@ -92,7 +92,7 @@ |
4100 | node = factory.make_Node_with_Interface_on_Subnet( |
4101 | address='aa:bb:cc:dd:ee:ff') |
4102 | architecture = make_usable_architecture(self) |
4103 | - form = NodeWithMACAddressesForm( |
4104 | + form = MachineWithMACAddressesForm( |
4105 | data=self.make_params( |
4106 | mac_addresses=['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f'], |
4107 | architecture=architecture), instance=node) |
4108 | @@ -107,7 +107,7 @@ |
4109 | factory.make_Node_with_Interface_on_Subnet(address='aa:bb:cc:dd:ee:ff') |
4110 | architecture = make_usable_architecture(self) |
4111 | node = factory.make_Node_with_Interface_on_Subnet() |
4112 | - form = NodeWithMACAddressesForm( |
4113 | + form = MachineWithMACAddressesForm( |
4114 | data=self.make_params( |
4115 | mac_addresses=['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f'], |
4116 | architecture=architecture), instance=node) |
4117 | @@ -119,7 +119,7 @@ |
4118 | factory.make_Interface( |
4119 | INTERFACE_TYPE.UNKNOWN, mac_address='aa:bb:cc:dd:ee:ff') |
4120 | architecture = make_usable_architecture(self) |
4121 | - form = NodeWithMACAddressesForm( |
4122 | + form = MachineWithMACAddressesForm( |
4123 | data=self.make_params( |
4124 | mac_addresses=['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f'], |
4125 | architecture=architecture)) |
4126 | @@ -132,7 +132,7 @@ |
4127 | |
4128 | def test__empty(self): |
4129 | # Empty values in the list of MAC addresses are simply ignored. |
4130 | - form = NodeWithMACAddressesForm( |
4131 | + form = MachineWithMACAddressesForm( |
4132 | data=self.make_params( |
4133 | mac_addresses=[factory.make_mac_address(), ''])) |
4134 | |
4135 | @@ -140,7 +140,7 @@ |
4136 | |
4137 | def test__save(self): |
4138 | macs = ['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f'] |
4139 | - form = NodeWithMACAddressesForm( |
4140 | + form = MachineWithMACAddressesForm( |
4141 | data=self.make_params(mac_addresses=macs)) |
4142 | node = form.save() |
4143 | |
4144 | @@ -150,13 +150,13 @@ |
4145 | [nic.mac_address for nic in node.interface_set.all()]) |
4146 | |
4147 | def test_form_without_hostname_generates_hostname(self): |
4148 | - form = NodeWithMACAddressesForm(data=self.make_params(hostname='')) |
4149 | + form = MachineWithMACAddressesForm(data=self.make_params(hostname='')) |
4150 | node = form.save() |
4151 | self.assertTrue(len(node.hostname) > 0) |
4152 | |
4153 | def test_form_with_ip_based_hostname_generates_hostname(self): |
4154 | ip_based_hostname = '192-168-12-10.maas' |
4155 | - form = NodeWithMACAddressesForm( |
4156 | + form = MachineWithMACAddressesForm( |
4157 | data=self.make_params(hostname=ip_based_hostname)) |
4158 | node = form.save() |
4159 | self.assertNotEqual(ip_based_hostname, node.hostname) |
4160 | |
4161 | === added file 'src/maasserver/tests/test_forms_node.py' |
4162 | --- src/maasserver/tests/test_forms_node.py 1970-01-01 00:00:00 +0000 |
4163 | +++ src/maasserver/tests/test_forms_node.py 2016-03-02 18:21:51 +0000 |
4164 | @@ -0,0 +1,204 @@ |
4165 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
4166 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
4167 | + |
4168 | +"""Tests for node forms.""" |
4169 | + |
4170 | +__all__ = [] |
4171 | + |
4172 | +from django.core.exceptions import ValidationError |
4173 | +from maasserver.forms import ( |
4174 | + AdminNodeForm, |
4175 | + NodeChoiceField, |
4176 | + NodeForm, |
4177 | +) |
4178 | +from maasserver.models import Node |
4179 | +from maasserver.testing.architecture import ( |
4180 | + make_usable_architecture, |
4181 | + patch_usable_architectures, |
4182 | +) |
4183 | +from maasserver.testing.factory import factory |
4184 | +from maasserver.testing.testcase import MAASServerTestCase |
4185 | +from maasserver.utils.orm import reload_object |
4186 | + |
4187 | + |
4188 | +class TestNodeForm(MAASServerTestCase): |
4189 | + def test_contains_limited_set_of_fields(self): |
4190 | + form = NodeForm() |
4191 | + |
4192 | + self.assertItemsEqual( |
4193 | + [ |
4194 | + 'hostname', |
4195 | + 'domain', |
4196 | + 'disable_ipv4', |
4197 | + 'swap_size', |
4198 | + ], list(form.fields)) |
4199 | + |
4200 | + def test_accepts_hostname(self): |
4201 | + machine = factory.make_Node() |
4202 | + hostname = factory.make_string() |
4203 | + patch_usable_architectures(self, [machine.architecture]) |
4204 | + |
4205 | + form = NodeForm( |
4206 | + data={ |
4207 | + 'hostname': hostname, |
4208 | + 'architecture': make_usable_architecture(self), |
4209 | + }, |
4210 | + instance=machine) |
4211 | + form.save() |
4212 | + |
4213 | + self.assertEqual(hostname, machine.hostname) |
4214 | + |
4215 | + def test_accepts_domain_by_name(self): |
4216 | + machine = factory.make_Node() |
4217 | + domain = factory.make_Domain() |
4218 | + patch_usable_architectures(self, [machine.architecture]) |
4219 | + |
4220 | + form = NodeForm( |
4221 | + data={ |
4222 | + 'domain': domain.name, |
4223 | + }, |
4224 | + instance=machine) |
4225 | + form.save() |
4226 | + |
4227 | + self.assertEqual(domain.name, machine.domain.name) |
4228 | + |
4229 | + def test_accepts_domain_by_id(self): |
4230 | + machine = factory.make_Node() |
4231 | + domain = factory.make_Domain() |
4232 | + patch_usable_architectures(self, [machine.architecture]) |
4233 | + |
4234 | + form = NodeForm( |
4235 | + data={ |
4236 | + 'domain': domain.id, |
4237 | + }, |
4238 | + instance=machine) |
4239 | + form.save() |
4240 | + |
4241 | + self.assertEqual(domain.name, machine.domain.name) |
4242 | + |
4243 | + def test_validates_domain(self): |
4244 | + machine = factory.make_Node() |
4245 | + patch_usable_architectures(self, [machine.architecture]) |
4246 | + |
4247 | + form = NodeForm( |
4248 | + data={ |
4249 | + 'domain': factory.make_name('domain'), |
4250 | + }, |
4251 | + instance=machine) |
4252 | + |
4253 | + self.assertFalse(form.is_valid()) |
4254 | + |
4255 | + def test_obeys_disable_ipv4_if_given(self): |
4256 | + setting = factory.pick_bool() |
4257 | + form = NodeForm( |
4258 | + data={ |
4259 | + 'architecture': make_usable_architecture(self), |
4260 | + 'disable_ipv4': setting, |
4261 | + }) |
4262 | + node = form.save() |
4263 | + self.assertEqual(setting, node.disable_ipv4) |
4264 | + |
4265 | + def test_takes_missing_disable_ipv4_as_False_in_UI(self): |
4266 | + form = NodeForm( |
4267 | + instance=factory.make_Node(disable_ipv4=True), |
4268 | + data={ |
4269 | + 'architecture': make_usable_architecture(self), |
4270 | + 'ui_submission': True, |
4271 | + }) |
4272 | + node = form.save() |
4273 | + self.assertFalse(node.disable_ipv4) |
4274 | + |
4275 | + def test_takes_missing_disable_ipv4_as_Unchanged_in_API(self): |
4276 | + form = NodeForm( |
4277 | + instance=factory.make_Node(disable_ipv4=True), |
4278 | + data={ |
4279 | + 'architecture': make_usable_architecture(self), |
4280 | + }) |
4281 | + node = form.save() |
4282 | + self.assertTrue(node.disable_ipv4) |
4283 | + |
4284 | + |
4285 | +class TestAdminNodeForm(MAASServerTestCase): |
4286 | + |
4287 | + def test_contains_limited_set_of_fields(self): |
4288 | + self.client_log_in() |
4289 | + node = factory.make_Node(owner=self.logged_in_user) |
4290 | + form = AdminNodeForm(instance=node) |
4291 | + |
4292 | + self.assertItemsEqual( |
4293 | + [ |
4294 | + 'hostname', |
4295 | + 'domain', |
4296 | + 'disable_ipv4', |
4297 | + 'swap_size', |
4298 | + 'cpu_count', |
4299 | + 'memory', |
4300 | + 'zone', |
4301 | + ], |
4302 | + list(form.fields)) |
4303 | + |
4304 | + def test_initialises_zone(self): |
4305 | + # The zone field uses "to_field_name", so that it can refer to a zone |
4306 | + # by name instead of by ID. A bug in Django breaks initialisation |
4307 | + # from an instance: the field tries to initialise the field using a |
4308 | + # zone's ID instead of its name, and ends up reverting to the default. |
4309 | + # The code must work around this bug. |
4310 | + zone = factory.make_Zone() |
4311 | + node = factory.make_Node(zone=zone) |
4312 | + # We'll create a form that makes a change, but not to the zone. |
4313 | + data = {'hostname': factory.make_name('host')} |
4314 | + form = AdminNodeForm(instance=node, data=data) |
4315 | + # The Django bug would stop the initial field value from being set, |
4316 | + # but the workaround ensures that it is initialised. |
4317 | + self.assertEqual(zone.name, form.initial['zone']) |
4318 | + |
4319 | + def test_changes_zone(self): |
4320 | + node = factory.make_Node() |
4321 | + zone = factory.make_Zone() |
4322 | + hostname = factory.make_string() |
4323 | + form = AdminNodeForm( |
4324 | + data={ |
4325 | + 'hostname': hostname, |
4326 | + 'architecture': make_usable_architecture(self), |
4327 | + 'zone': zone.name, |
4328 | + }, |
4329 | + instance=node) |
4330 | + form.save() |
4331 | + |
4332 | + node = reload_object(node) |
4333 | + self.assertEqual(node.hostname, hostname) |
4334 | + self.assertEqual(node.zone, zone) |
4335 | + |
4336 | + |
4337 | +class TestNodeChoiceField(MAASServerTestCase): |
4338 | + def test_allows_selecting_by_system_id(self): |
4339 | + node = factory.make_Node() |
4340 | + for _ in range(3): |
4341 | + factory.make_Node() |
4342 | + node_field = NodeChoiceField(Node.objects.filter()) |
4343 | + self.assertEqual(node, node_field.clean(node.system_id)) |
4344 | + |
4345 | + def test_allows_selecting_by_hostname(self): |
4346 | + node = factory.make_Node() |
4347 | + for _ in range(3): |
4348 | + factory.make_Node() |
4349 | + node_field = NodeChoiceField(Node.objects.filter()) |
4350 | + self.assertEqual(node, node_field.clean(node.hostname)) |
4351 | + |
4352 | + def test_raises_exception_when_not_found(self): |
4353 | + for _ in range(3): |
4354 | + factory.make_Node() |
4355 | + node_field = NodeChoiceField(Node.objects.filter()) |
4356 | + self.assertRaises( |
4357 | + ValidationError, node_field.clean, factory.make_name('query')) |
4358 | + |
4359 | + def test_works_with_multiple_entries_in_queryset(self): |
4360 | + # Regression test for lp:1551399 |
4361 | + vlan = factory.make_VLAN() |
4362 | + node = factory.make_Node_with_Interface_on_Subnet(vlan=vlan) |
4363 | + factory.make_Interface(node=node, vlan=vlan) |
4364 | + qs = Node.objects.filter_by_vids([vlan.vid]) |
4365 | + node_field = NodeChoiceField(qs) |
4366 | + # Double check that we have duplicated entires |
4367 | + self.assertEqual(2, len(qs.filter(system_id=node.system_id))) |
4368 | + self.assertEqual(node, node_field.clean(node.system_id)) |
4369 | |
4370 | === modified file 'src/maasserver/websockets/handlers/controller.py' |
4371 | --- src/maasserver/websockets/handlers/controller.py 2016-03-01 19:02:08 +0000 |
4372 | +++ src/maasserver/websockets/handlers/controller.py 2016-03-02 18:21:51 +0000 |
4373 | @@ -8,7 +8,7 @@ |
4374 | ] |
4375 | |
4376 | from maasserver.enum import NODE_PERMISSION |
4377 | -from maasserver.forms import AdminNodeWithMACAddressesForm |
4378 | +from maasserver.forms import AdminMachineWithMACAddressesForm |
4379 | from maasserver.models.node import Node |
4380 | from maasserver.websockets.handlers.machine import MachineHandler |
4381 | from maasserver.websockets.handlers.node import node_prefetch |
4382 | @@ -36,7 +36,7 @@ |
4383 | 'link_subnet', |
4384 | 'unlink_subnet', |
4385 | ] |
4386 | - form = AdminNodeWithMACAddressesForm |
4387 | + form = AdminMachineWithMACAddressesForm |
4388 | exclude = [ |
4389 | "status_expires", |
4390 | "parent", |
4391 | |
4392 | === modified file 'src/maasserver/websockets/handlers/machine.py' |
4393 | --- src/maasserver/websockets/handlers/machine.py 2016-03-02 10:02:12 +0000 |
4394 | +++ src/maasserver/websockets/handlers/machine.py 2016-03-02 18:21:51 +0000 |
4395 | @@ -19,7 +19,7 @@ |
4396 | from maasserver.exceptions import NodeActionError |
4397 | from maasserver.forms import ( |
4398 | AddPartitionForm, |
4399 | - AdminNodeWithMACAddressesForm, |
4400 | + AdminMachineWithMACAddressesForm, |
4401 | CreateBcacheForm, |
4402 | CreateCacheSetForm, |
4403 | CreateLogicalVolumeForm, |
4404 | @@ -118,7 +118,7 @@ |
4405 | 'create_logical_volume', |
4406 | 'set_boot_disk', |
4407 | ] |
4408 | - form = AdminNodeWithMACAddressesForm |
4409 | + form = AdminMachineWithMACAddressesForm |
4410 | exclude = [ |
4411 | "status_expires", |
4412 | "parent", |
4413 | @@ -202,7 +202,7 @@ |
4414 | def get_form_class(self, action): |
4415 | """Return the form class used for `action`.""" |
4416 | if action in ("create", "update"): |
4417 | - return AdminNodeWithMACAddressesForm |
4418 | + return AdminMachineWithMACAddressesForm |
4419 | else: |
4420 | raise HandlerError("Unknown action: %s" % action) |
4421 | |
4422 | |
4423 | === modified file 'src/maasserver/websockets/handlers/tests/test_controller.py' |
4424 | --- src/maasserver/websockets/handlers/tests/test_controller.py 2016-03-01 19:02:08 +0000 |
4425 | +++ src/maasserver/websockets/handlers/tests/test_controller.py 2016-03-02 18:21:51 +0000 |
4426 | @@ -6,7 +6,7 @@ |
4427 | __all__ = [] |
4428 | |
4429 | from maasserver.enum import NODE_TYPE |
4430 | -from maasserver.forms import AdminNodeWithMACAddressesForm |
4431 | +from maasserver.forms import AdminMachineWithMACAddressesForm |
4432 | from maasserver.testing.factory import factory |
4433 | from maasserver.testing.testcase import MAASServerTestCase |
4434 | from maasserver.websockets.handlers.controller import ControllerHandler |
4435 | @@ -63,12 +63,12 @@ |
4436 | user = factory.make_admin() |
4437 | handler = ControllerHandler(user, {}) |
4438 | self.assertEqual( |
4439 | - AdminNodeWithMACAddressesForm, |
4440 | + AdminMachineWithMACAddressesForm, |
4441 | handler.get_form_class("create")) |
4442 | |
4443 | def test_get_form_class_for_update(self): |
4444 | user = factory.make_admin() |
4445 | handler = ControllerHandler(user, {}) |
4446 | self.assertEqual( |
4447 | - AdminNodeWithMACAddressesForm, |
4448 | + AdminMachineWithMACAddressesForm, |
4449 | handler.get_form_class("update")) |
4450 | |
4451 | === modified file 'src/maasserver/websockets/handlers/tests/test_machine.py' |
4452 | --- src/maasserver/websockets/handlers/tests/test_machine.py 2016-03-02 02:22:45 +0000 |
4453 | +++ src/maasserver/websockets/handlers/tests/test_machine.py 2016-03-02 18:21:51 +0000 |
4454 | @@ -26,7 +26,7 @@ |
4455 | NODE_TYPE, |
4456 | ) |
4457 | from maasserver.exceptions import NodeActionError |
4458 | -from maasserver.forms import AdminNodeWithMACAddressesForm |
4459 | +from maasserver.forms import AdminMachineWithMACAddressesForm |
4460 | from maasserver.models.blockdevice import BlockDevice |
4461 | from maasserver.models.cacheset import CacheSet |
4462 | from maasserver.models.config import Config |
4463 | @@ -898,14 +898,14 @@ |
4464 | user = factory.make_admin() |
4465 | handler = MachineHandler(user, {}) |
4466 | self.assertEqual( |
4467 | - AdminNodeWithMACAddressesForm, |
4468 | + AdminMachineWithMACAddressesForm, |
4469 | handler.get_form_class("create")) |
4470 | |
4471 | def test_get_form_class_for_update(self): |
4472 | user = factory.make_admin() |
4473 | handler = MachineHandler(user, {}) |
4474 | self.assertEqual( |
4475 | - AdminNodeWithMACAddressesForm, |
4476 | + AdminMachineWithMACAddressesForm, |
4477 | handler.get_form_class("update")) |
4478 | |
4479 | def test_get_form_class_raises_error_for_unknown_action(self): |
4480 | |
4481 | === modified file 'src/maasserver/websockets/tests/test_base.py' |
4482 | --- src/maasserver/websockets/tests/test_base.py 2016-02-26 18:39:26 +0000 |
4483 | +++ src/maasserver/websockets/tests/test_base.py 2016-03-02 18:21:51 +0000 |
4484 | @@ -9,8 +9,8 @@ |
4485 | |
4486 | from django.db.models.query import QuerySet |
4487 | from maasserver.forms import ( |
4488 | - AdminNodeForm, |
4489 | - AdminNodeWithMACAddressesForm, |
4490 | + AdminMachineForm, |
4491 | + AdminMachineWithMACAddressesForm, |
4492 | ) |
4493 | from maasserver.models.node import Node |
4494 | from maasserver.models.zone import Zone |
4495 | @@ -446,7 +446,7 @@ |
4496 | arch = make_usable_architecture(self) |
4497 | handler = self.make_nodes_handler( |
4498 | fields=['hostname', 'architecture'], |
4499 | - form=AdminNodeWithMACAddressesForm) |
4500 | + form=AdminMachineWithMACAddressesForm) |
4501 | json_obj = handler.create({ |
4502 | "hostname": hostname, |
4503 | "architecture": arch, |
4504 | @@ -464,7 +464,7 @@ |
4505 | fields=['hostname', 'architecture']) |
4506 | self.patch( |
4507 | handler, |
4508 | - "get_form_class").return_value = AdminNodeWithMACAddressesForm |
4509 | + "get_form_class").return_value = AdminMachineWithMACAddressesForm |
4510 | json_obj = handler.create({ |
4511 | "hostname": hostname, |
4512 | "architecture": arch, |
4513 | @@ -495,7 +495,7 @@ |
4514 | arch = make_usable_architecture(self) |
4515 | handler = self.make_nodes_handler( |
4516 | fields=['hostname', 'architecture'], |
4517 | - form=AdminNodeWithMACAddressesForm) |
4518 | + form=AdminMachineWithMACAddressesForm) |
4519 | self.assertRaises( |
4520 | HandlerValidationError, handler.create, { |
4521 | "hostname": hostname, |
4522 | @@ -521,7 +521,7 @@ |
4523 | node = factory.make_Node(architecture=arch) |
4524 | hostname = factory.make_name("hostname") |
4525 | handler = self.make_nodes_handler( |
4526 | - fields=['hostname'], form=AdminNodeForm) |
4527 | + fields=['hostname'], form=AdminMachineForm) |
4528 | json_obj = handler.update({ |
4529 | "system_id": node.system_id, |
4530 | "hostname": hostname, |
4531 | @@ -539,7 +539,7 @@ |
4532 | handler = self.make_nodes_handler(fields=['hostname']) |
4533 | self.patch( |
4534 | handler, |
4535 | - "get_form_class").return_value = AdminNodeForm |
4536 | + "get_form_class").return_value = AdminMachineForm |
4537 | json_obj = handler.update({ |
4538 | "system_id": node.system_id, |
4539 | "hostname": hostname, |
4540 | |
4541 | === modified file 'src/provisioningserver/drivers/hardware/msftocs.py' |
4542 | --- src/provisioningserver/drivers/hardware/msftocs.py 2015-12-01 18:12:59 +0000 |
4543 | +++ src/provisioningserver/drivers/hardware/msftocs.py 2016-03-02 18:21:51 +0000 |
4544 | @@ -188,7 +188,7 @@ |
4545 | |
4546 | @synchronous |
4547 | def probe_and_enlist_msftocs( |
4548 | - user, ip, port, username, password, accept_all=False): |
4549 | + user, ip, port, username, password, accept_all=False, domain=None): |
4550 | """ Extracts all of nodes from msftocs, sets all of them to boot via |
4551 | HDD by, default, sets them to bootonce via PXE, and then enlists them |
4552 | into MAAS. |
4553 | @@ -223,7 +223,8 @@ |
4554 | 'power_pass': password, |
4555 | 'blade_id': blade_id, |
4556 | } |
4557 | - system_id = create_node(macs, 'amd64', 'msftocs', params).wait(30) |
4558 | + system_id = create_node( |
4559 | + macs, 'amd64', 'msftocs', params, domain).wait(30) |
4560 | |
4561 | if accept_all: |
4562 | commission_node(system_id, user).wait(30) |
4563 | |
4564 | === modified file 'src/provisioningserver/drivers/hardware/seamicro.py' |
4565 | --- src/provisioningserver/drivers/hardware/seamicro.py 2015-12-01 18:12:59 +0000 |
4566 | +++ src/provisioningserver/drivers/hardware/seamicro.py 2016-03-02 18:21:51 +0000 |
4567 | @@ -275,8 +275,9 @@ |
4568 | |
4569 | |
4570 | @synchronous |
4571 | -def probe_seamicro15k_and_enlist(user, ip, username, password, |
4572 | - power_control=None, accept_all=False): |
4573 | +def probe_seamicro15k_and_enlist( |
4574 | + user, ip, username, password, power_control=None, accept_all=False, |
4575 | + domain=None): |
4576 | power_control = power_control or 'ipmi' |
4577 | |
4578 | maaslog.info("Probing for seamicro15k servers as %s@%s", username, ip) |
4579 | @@ -292,7 +293,8 @@ |
4580 | 'system_id': system_id |
4581 | } |
4582 | maaslog.info("Creating seamicro15k node with MACs: %s", macs) |
4583 | - system_id = create_node(macs, 'amd64', 'sm15k', params).wait(30) |
4584 | + system_id = create_node( |
4585 | + macs, 'amd64', 'sm15k', params, domain).wait(30) |
4586 | |
4587 | if accept_all: |
4588 | commission_node(system_id, user).wait(30) |
4589 | |
4590 | === modified file 'src/provisioningserver/drivers/hardware/tests/test_msftocs.py' |
4591 | --- src/provisioningserver/drivers/hardware/tests/test_msftocs.py 2015-12-01 18:12:59 +0000 |
4592 | +++ src/provisioningserver/drivers/hardware/tests/test_msftocs.py 2016-03-02 18:21:51 +0000 |
4593 | @@ -366,6 +366,7 @@ |
4594 | port = randint(2000, 4000) |
4595 | username = factory.make_name('username') |
4596 | password = factory.make_name('password') |
4597 | + domain = factory.make_name('domain') |
4598 | system_id = factory.make_name('system_id') |
4599 | blade_id = randint(1, 24) |
4600 | macs = ['F4:52:14:D6:70:98', 'F4:52:14:D6:70:99'] |
4601 | @@ -385,13 +386,13 @@ |
4602 | } |
4603 | yield deferToThread( |
4604 | probe_and_enlist_msftocs, user, ip, port, username, |
4605 | - password, accept_all=True) |
4606 | + password, True, domain) |
4607 | |
4608 | self.expectThat(blades_mock, MockAnyCall()) |
4609 | self.expectThat(next_boot_device_mock.call_count, Equals(2)) |
4610 | self.expectThat( |
4611 | create_node_mock, |
4612 | - MockCalledOnceWith(macs, 'amd64', 'msftocs', params)) |
4613 | + MockCalledOnceWith(macs, 'amd64', 'msftocs', params, domain)) |
4614 | self.expectThat( |
4615 | commission_node_mock, |
4616 | MockCalledOnceWith(system_id, user)) |
4617 | |
4618 | === modified file 'src/provisioningserver/drivers/hardware/tests/test_seamicro.py' |
4619 | --- src/provisioningserver/drivers/hardware/tests/test_seamicro.py 2015-12-01 18:12:59 +0000 |
4620 | +++ src/provisioningserver/drivers/hardware/tests/test_seamicro.py 2016-03-02 18:21:51 +0000 |
4621 | @@ -311,6 +311,7 @@ |
4622 | username = factory.make_name('username') |
4623 | password = factory.make_name('password') |
4624 | system_id = factory.make_name('system_id') |
4625 | + domain = factory.make_name('domain') |
4626 | result = { |
4627 | 0: { |
4628 | 'serverId': '0/0', |
4629 | @@ -343,7 +344,7 @@ |
4630 | yield deferToThread( |
4631 | probe_seamicro15k_and_enlist, |
4632 | user, ip, username, password, |
4633 | - power_control='restapi', accept_all=True) |
4634 | + power_control='restapi', accept_all=True, domain=domain) |
4635 | self.assertEqual(3, mock_create_node.call_count) |
4636 | |
4637 | last = result[2] |
4638 | @@ -357,7 +358,7 @@ |
4639 | self.expectThat( |
4640 | mock_create_node, |
4641 | MockCalledWith( |
4642 | - last['serverMacAddr'], 'amd64', 'sm15k', power_params)) |
4643 | + last['serverMacAddr'], 'amd64', 'sm15k', power_params, domain)) |
4644 | self.expectThat( |
4645 | mock_commission_node, |
4646 | MockCalledWith(system_id, user)) |
4647 | |
4648 | === modified file 'src/provisioningserver/drivers/hardware/tests/test_ucsm.py' |
4649 | --- src/provisioningserver/drivers/hardware/tests/test_ucsm.py 2015-12-01 18:12:59 +0000 |
4650 | +++ src/provisioningserver/drivers/hardware/tests/test_ucsm.py 2016-03-02 18:21:51 +0000 |
4651 | @@ -618,6 +618,7 @@ |
4652 | username = factory.make_name('username') |
4653 | password = factory.make_name('password') |
4654 | system_id = factory.make_name('system_id') |
4655 | + domain = factory.make_name('domain') |
4656 | api = Mock() |
4657 | self.patch(ucsm, 'UCSM_XML_API').return_value = api |
4658 | server_element = {'uuid': 'uuid'} |
4659 | @@ -631,7 +632,7 @@ |
4660 | |
4661 | yield deferToThread( |
4662 | probe_and_enlist_ucsm, user, url, username, |
4663 | - password, accept_all=True) |
4664 | + password, True, domain) |
4665 | self.expectThat( |
4666 | set_lan_boot_default_mock, |
4667 | MockCalledOnceWith(api, server_element)) |
4668 | @@ -644,7 +645,7 @@ |
4669 | } |
4670 | self.expectThat( |
4671 | create_node_mock, |
4672 | - MockCalledOnceWith(server[1], 'amd64', 'ucsm', params)) |
4673 | + MockCalledOnceWith(server[1], 'amd64', 'ucsm', params, domain)) |
4674 | self.expectThat( |
4675 | commission_node_mock, |
4676 | MockCalledOnceWith(system_id, user)) |
4677 | |
4678 | === modified file 'src/provisioningserver/drivers/hardware/tests/test_virsh.py' |
4679 | --- src/provisioningserver/drivers/hardware/tests/test_virsh.py 2015-12-10 05:00:21 +0000 |
4680 | +++ src/provisioningserver/drivers/hardware/tests/test_virsh.py 2016-03-02 18:21:51 +0000 |
4681 | @@ -1,4 +1,4 @@ |
4682 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
4683 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
4684 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4685 | |
4686 | """Tests for `provisioningserver.drivers.hardware.virsh`. |
4687 | @@ -271,6 +271,7 @@ |
4688 | fake_arch = factory.make_name('arch') |
4689 | mock_arch = self.patch(virsh.VirshSSH, 'get_arch') |
4690 | mock_arch.return_value = fake_arch |
4691 | + domain = factory.make_name('domain') |
4692 | |
4693 | # Patch get_state so that one of the machines is on, so we |
4694 | # can check that it will be forced off. |
4695 | @@ -330,7 +331,7 @@ |
4696 | # Perform the probe and enlist |
4697 | yield deferToThread( |
4698 | virsh.probe_virsh_and_enlist, user, poweraddr, |
4699 | - password=fake_password, accept_all=True) |
4700 | + fake_password, True, domain) |
4701 | |
4702 | # Check that login was called with the provided poweraddr and |
4703 | # password. |
4704 | @@ -351,16 +352,16 @@ |
4705 | mock_create_node, MockCallsMatch( |
4706 | call( |
4707 | fake_macs[0], fake_arch, 'virsh', called_params[0], |
4708 | - machines[0]), |
4709 | + domain, machines[0]), |
4710 | call( |
4711 | fake_macs[1], fake_arch, 'virsh', called_params[1], |
4712 | - machines[1]), |
4713 | + domain, machines[1]), |
4714 | call( |
4715 | fake_macs[2], fake_arch, 'virsh', called_params[2], |
4716 | - machines[2]), |
4717 | + domain, machines[2]), |
4718 | call( |
4719 | fake_macs[3], fake_arch, 'virsh', called_params[3], |
4720 | - machines[3]), |
4721 | + domain, machines[3]), |
4722 | )) |
4723 | self.assertThat(mock_logout, MockCalledOnceWith()) |
4724 | self.expectThat( |
4725 | |
4726 | === modified file 'src/provisioningserver/drivers/hardware/ucsm.py' |
4727 | --- src/provisioningserver/drivers/hardware/ucsm.py 2015-12-01 18:12:59 +0000 |
4728 | +++ src/provisioningserver/drivers/hardware/ucsm.py 2016-03-02 18:21:51 +0000 |
4729 | @@ -432,7 +432,8 @@ |
4730 | |
4731 | |
4732 | @synchronous |
4733 | -def probe_and_enlist_ucsm(user, url, username, password, accept_all=False): |
4734 | +def probe_and_enlist_ucsm( |
4735 | + user, url, username, password, accept_all=False, domain=None): |
4736 | """Probe a UCS Manager and enlist all its servers. |
4737 | |
4738 | Here's what happens here: 1. Get a list of servers from the UCS |
4739 | @@ -474,7 +475,8 @@ |
4740 | 'power_pass': password, |
4741 | 'uuid': server.get('uuid'), |
4742 | } |
4743 | - system_id = create_node(macs, 'amd64', 'ucsm', params).wait(30) |
4744 | + system_id = create_node( |
4745 | + macs, 'amd64', 'ucsm', params, domain).wait(30) |
4746 | |
4747 | if accept_all: |
4748 | commission_node(system_id, user).wait(30) |
4749 | |
4750 | === modified file 'src/provisioningserver/drivers/hardware/virsh.py' |
4751 | --- src/provisioningserver/drivers/hardware/virsh.py 2015-12-11 18:18:23 +0000 |
4752 | +++ src/provisioningserver/drivers/hardware/virsh.py 2016-03-02 18:21:51 +0000 |
4753 | @@ -257,8 +257,9 @@ |
4754 | |
4755 | |
4756 | @synchronous |
4757 | -def probe_virsh_and_enlist(user, poweraddr, password=None, |
4758 | - prefix_filter=None, accept_all=False): |
4759 | +def probe_virsh_and_enlist( |
4760 | + user, poweraddr, password=None, prefix_filter=None, accept_all=False, |
4761 | + domain=None): |
4762 | """Extracts all of the VMs from virsh and enlists them |
4763 | into MAAS. |
4764 | |
4765 | @@ -267,6 +268,7 @@ |
4766 | :param password: password connection string. |
4767 | :param prefix_filter: only enlist nodes that have the prefix. |
4768 | :param accept_all: if True, commission enlisted nodes. |
4769 | + :param domain: The domain for the node to join. |
4770 | """ |
4771 | conn = VirshSSH(dom_prefix=prefix_filter) |
4772 | if not conn.login(poweraddr, password): |
4773 | @@ -289,7 +291,7 @@ |
4774 | if password is not None: |
4775 | params['power_pass'] = password |
4776 | system_id = create_node( |
4777 | - macs, arch, 'virsh', params, hostname=machine).wait(30) |
4778 | + macs, arch, 'virsh', params, domain, machine).wait(30) |
4779 | |
4780 | if system_id is not None: |
4781 | conn.configure_pxe_boot(machine) |
4782 | |
4783 | === modified file 'src/provisioningserver/drivers/hardware/vmware.py' |
4784 | --- src/provisioningserver/drivers/hardware/vmware.py 2016-01-19 22:36:17 +0000 |
4785 | +++ src/provisioningserver/drivers/hardware/vmware.py 2016-03-02 18:21:51 +0000 |
4786 | @@ -1,4 +1,4 @@ |
4787 | -# Copyright 2015 Canonical Ltd. This software is licensed under the |
4788 | +# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
4789 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4790 | |
4791 | __all__ = [ |
4792 | @@ -353,7 +353,7 @@ |
4793 | @synchronous |
4794 | def probe_vmware_and_enlist( |
4795 | user, host, username, password, port=None, |
4796 | - protocol=None, prefix_filter=None, accept_all=False): |
4797 | + protocol=None, prefix_filter=None, accept_all=False, domain=None): |
4798 | |
4799 | # Both '' and None mean the same thing, so normalize it. |
4800 | if prefix_filter is None: |
4801 | @@ -367,14 +367,14 @@ |
4802 | servers = api.get_all_vm_properties() |
4803 | _probe_and_enlist_vmware_servers( |
4804 | api, accept_all, host, password, port, prefix_filter, protocol, |
4805 | - servers, user, username) |
4806 | + servers, user, username, domain) |
4807 | finally: |
4808 | api.disconnect() |
4809 | |
4810 | |
4811 | def _probe_and_enlist_vmware_servers( |
4812 | api, accept_all, host, password, port, prefix_filter, protocol, |
4813 | - servers, user, username): |
4814 | + servers, user, username, domain): |
4815 | maaslog.info("Found %d VMware servers", len(servers)) |
4816 | for system_name in servers: |
4817 | if not system_name.startswith(prefix_filter): |
4818 | @@ -403,7 +403,7 @@ |
4819 | |
4820 | system_id = create_node( |
4821 | properties['macs'], properties['architecture'], |
4822 | - 'vmware', params, hostname=system_name).wait(30) |
4823 | + 'vmware', params, domain, system_name).wait(30) |
4824 | |
4825 | if system_id is not None: |
4826 | api.set_pxe_boot(properties) |
4827 | |
4828 | === modified file 'src/provisioningserver/drivers/power/mscm.py' |
4829 | --- src/provisioningserver/drivers/power/mscm.py 2016-02-25 16:13:34 +0000 |
4830 | +++ src/provisioningserver/drivers/power/mscm.py 2016-03-02 18:21:51 +0000 |
4831 | @@ -141,7 +141,8 @@ |
4832 | |
4833 | |
4834 | @synchronous |
4835 | -def probe_and_enlist_mscm(user, host, username, password, accept_all=False): |
4836 | +def probe_and_enlist_mscm( |
4837 | + user, host, username, password, accept_all=False, domain=None): |
4838 | """ Extracts all of nodes from the MSCM, sets all of them to boot via M.2 |
4839 | by, default, sets them to bootonce via PXE, and then enlists them into |
4840 | MAAS. If accept_all is True, it will also commission them. |
4841 | @@ -201,7 +202,7 @@ |
4842 | "show node macaddr %s" % node_id, **params) |
4843 | macs = re.findall(r':'.join(['[0-9a-f]{2}'] * 6), node_macaddr) |
4844 | # Create node |
4845 | - system_id = create_node(macs, arch, 'mscm', params).wait(30) |
4846 | + system_id = create_node(macs, arch, 'mscm', params, domain).wait(30) |
4847 | |
4848 | if accept_all: |
4849 | commission_node(system_id, user).wait(30) |
4850 | |
4851 | === modified file 'src/provisioningserver/drivers/power/tests/test_mscm.py' |
4852 | --- src/provisioningserver/drivers/power/tests/test_mscm.py 2016-02-25 16:13:34 +0000 |
4853 | +++ src/provisioningserver/drivers/power/tests/test_mscm.py 2016-03-02 18:21:51 +0000 |
4854 | @@ -248,6 +248,7 @@ |
4855 | host = factory.make_hostname('mscm') |
4856 | username = factory.make_name('user') |
4857 | password = factory.make_name('password') |
4858 | + domain = factory.make_name('domain') |
4859 | system_id = factory.make_name('system_id') |
4860 | Driver = self.patch(mscm_module, "MSCMPowerDriver") |
4861 | mscm_driver = Driver.return_value |
4862 | @@ -265,11 +266,11 @@ |
4863 | |
4864 | yield deferToThread( |
4865 | probe_and_enlist_mscm, |
4866 | - user, host, username, password, accept_all=True) |
4867 | + user, host, username, password, True, domain) |
4868 | |
4869 | self.expectThat( |
4870 | create_node, |
4871 | - MockCalledOnceWith(macs, self.arch, 'mscm', params)) |
4872 | + MockCalledOnceWith(macs, self.arch, 'mscm', params, domain)) |
4873 | self.expectThat( |
4874 | commission_node, |
4875 | MockCalledOnceWith(system_id, user)) |
4876 | |
4877 | === modified file 'src/provisioningserver/rpc/cluster.py' |
4878 | --- src/provisioningserver/rpc/cluster.py 2016-02-29 07:45:25 +0000 |
4879 | +++ src/provisioningserver/rpc/cluster.py 2016-03-02 18:21:51 +0000 |
4880 | @@ -419,6 +419,7 @@ |
4881 | (b"username", amp.Unicode(optional=True)), |
4882 | (b"password", amp.Unicode(optional=True)), |
4883 | (b"accept_all", amp.Boolean(optional=True)), |
4884 | + (b"domain", amp.Unicode(optional=True)), |
4885 | (b"prefix_filter", amp.Unicode(optional=True)), |
4886 | (b"power_control", amp.Unicode(optional=True)), |
4887 | (b"port", amp.Integer(optional=True)), |
4888 | |
4889 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
4890 | --- src/provisioningserver/rpc/clusterservice.py 2016-03-02 14:21:29 +0000 |
4891 | +++ src/provisioningserver/rpc/clusterservice.py 2016-03-02 18:21:51 +0000 |
4892 | @@ -1,4 +1,4 @@ |
4893 | -# Copyright 2014-2015 Canonical Ltd. This software is licensed under the |
4894 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
4895 | # GNU Affero General Public License version 3 (see the file LICENSE). |
4896 | |
4897 | """RPC implementation for clusters.""" |
4898 | @@ -380,8 +380,8 @@ |
4899 | @cluster.AddChassis.responder |
4900 | def add_chassis( |
4901 | self, user, chassis_type, hostname, username=None, password=None, |
4902 | - accept_all=False, prefix_filter=None, power_control=None, |
4903 | - port=None, protocol=None): |
4904 | + accept_all=False, domain=None, prefix_filter=None, |
4905 | + power_control=None, port=None, protocol=None): |
4906 | """AddChassis() |
4907 | |
4908 | Implementation of |
4909 | @@ -391,34 +391,36 @@ |
4910 | if chassis_type in ('virsh', 'powerkvm'): |
4911 | d = deferToThread( |
4912 | probe_virsh_and_enlist, |
4913 | - user, hostname, password, prefix_filter, accept_all) |
4914 | + user, hostname, password, prefix_filter, accept_all, |
4915 | + domain) |
4916 | d.addErrback(partial(catch_probe_and_enlist_error, "virsh")) |
4917 | elif chassis_type == 'vmware': |
4918 | d = deferToThread( |
4919 | probe_vmware_and_enlist, |
4920 | user, hostname, username, password, port, protocol, |
4921 | - prefix_filter, accept_all) |
4922 | + prefix_filter, accept_all, domain) |
4923 | d.addErrback(partial(catch_probe_and_enlist_error, "VMware")) |
4924 | elif chassis_type == 'seamicro15k': |
4925 | d = deferToThread( |
4926 | probe_seamicro15k_and_enlist, |
4927 | - user, hostname, username, password, power_control, accept_all) |
4928 | + user, hostname, username, password, power_control, accept_all, |
4929 | + domain) |
4930 | d.addErrback( |
4931 | partial(catch_probe_and_enlist_error, "SeaMicro 15000")) |
4932 | elif chassis_type == 'mscm': |
4933 | d = deferToThread( |
4934 | probe_and_enlist_mscm, user, hostname, username, password, |
4935 | - accept_all) |
4936 | + accept_all, domain) |
4937 | d.addErrback(partial(catch_probe_and_enlist_error, "Moonshot")) |
4938 | elif chassis_type == 'msftocs': |
4939 | d = deferToThread( |
4940 | probe_and_enlist_msftocs, user, hostname, port, username, |
4941 | - password, accept_all) |
4942 | + password, accept_all, domain) |
4943 | d.addErrback(partial(catch_probe_and_enlist_error, "MicrosoftOCS")) |
4944 | elif chassis_type == 'ucsm': |
4945 | d = deferToThread( |
4946 | probe_and_enlist_ucsm, user, hostname, username, password, |
4947 | - accept_all) |
4948 | + accept_all, domain) |
4949 | d.addErrback(partial(catch_probe_and_enlist_error, "UCS")) |
4950 | else: |
4951 | message = "Unknown chassis type %s" % chassis_type |
4952 | |
4953 | === modified file 'src/provisioningserver/rpc/region.py' |
4954 | --- src/provisioningserver/rpc/region.py 2016-02-26 16:19:41 +0000 |
4955 | +++ src/provisioningserver/rpc/region.py 2016-03-02 18:21:51 +0000 |
4956 | @@ -344,6 +344,7 @@ |
4957 | (b'power_parameters', amp.Unicode()), |
4958 | (b'mac_addresses', amp.ListOf(amp.Unicode())), |
4959 | (b'hostname', amp.Unicode(optional=True)), |
4960 | + (b'domain', amp.Unicode(optional=True)), |
4961 | ] |
4962 | response = [ |
4963 | (b'system_id', amp.Unicode()), |
4964 | |
4965 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
4966 | --- src/provisioningserver/rpc/tests/test_clusterservice.py 2016-03-02 14:21:29 +0000 |
4967 | +++ src/provisioningserver/rpc/tests/test_clusterservice.py 2016-03-02 18:21:51 +0000 |
4968 | @@ -1881,6 +1881,7 @@ |
4969 | hostname = factory.make_hostname() |
4970 | password = factory.make_name('password') |
4971 | accept_all = factory.pick_bool() |
4972 | + domain = factory.make_name('domain') |
4973 | prefix_filter = factory.make_name('prefix_filter') |
4974 | call_responder(Cluster(), cluster.AddChassis, { |
4975 | 'user': user, |
4976 | @@ -1888,12 +1889,14 @@ |
4977 | 'hostname': hostname, |
4978 | 'password': password, |
4979 | 'accept_all': accept_all, |
4980 | + 'domain': domain, |
4981 | 'prefix_filter': prefix_filter, |
4982 | }) |
4983 | self.assertThat( |
4984 | mock_deferToThread, MockCalledOnceWith( |
4985 | clusterservice.probe_virsh_and_enlist, |
4986 | - user, hostname, password, prefix_filter, accept_all)) |
4987 | + user, hostname, password, prefix_filter, accept_all, |
4988 | + domain)) |
4989 | |
4990 | def test_chassis_type_powerkvm_calls_probe_virsh_and_enlist(self): |
4991 | mock_deferToThread = self.patch_autospec( |
4992 | @@ -1902,6 +1905,7 @@ |
4993 | hostname = factory.make_hostname() |
4994 | password = factory.make_name('password') |
4995 | accept_all = factory.pick_bool() |
4996 | + domain = factory.make_name('domain') |
4997 | prefix_filter = factory.make_name('prefix_filter') |
4998 | call_responder(Cluster(), cluster.AddChassis, { |
4999 | 'user': user, |
5000 | @@ -1909,12 +1913,14 @@ |
This branch is so large I am not going to even look through it. I did look for specifics that I know needed to be fixed. I commented inline about "status" showing on rack controller, I don't think this should be the case. It will even be removed from the WebUI as well, so lets please remove status.
Also as discussed in the hangout remove "update" on node and on rack controllers. We can add those later if really needed and once that has been fully nailed down and supported.
You need to work on making the branches smaller, because this is impossible to review. You could have split this up into different pieces to make it easier on the reviewer. I am being nice today because we need this for alpha1, but if it was not for that I would probably give this a "Disapprove."
Here is a breakdown on how this could have been a few branches:
1. Update license headers.
2. Rename node to machine.
3. Fix fields shown for rack controllers.
4. Other extra fixes (if needed).
Another thing you need to work on is the commit message. The commit message is what shows up in the bzr log. The description is just for the review to read to know what you have done (only if the commit message can not tell them). So update your commit message as well to include a better idea of what this branch did to MAAS when it was merged.
Marking "approved" since this is needed for alpha1 and since we discussed it during the standup. Please, please, please next time split the work out to make it smaller. It really doesn't make it hard on you, if anything it helps you get eyes on the code sooner as you can go work on the next piece of work.