Merge lp:~julian-edwards/maas/api-doc-bug-1391193 into lp:~maas-committers/maas/trunk

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 3385
Proposed branch: lp:~julian-edwards/maas/api-doc-bug-1391193
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 712 lines (+225/-30)
11 files modified
src/maasserver/api/files.py (+5/-0)
src/maasserver/api/ip_addresses.py (+5/-0)
src/maasserver/api/node_group_interfaces.py (+18/-2)
src/maasserver/api/node_groups.py (+38/-5)
src/maasserver/api/node_macs.py (+17/-4)
src/maasserver/api/nodes.py (+85/-9)
src/maasserver/api/ssh_keys.py (+9/-2)
src/maasserver/api/ssl_keys.py (+10/-2)
src/maasserver/api/tags.py (+23/-3)
src/maasserver/api/users.py (+2/-0)
src/maasserver/api/zones.py (+13/-3)
To merge this branch: bzr merge lp:~julian-edwards/maas/api-doc-bug-1391193
Reviewer Review Type Date Requested Status
Newell Jensen (community) Approve
Review via email: mp+242543@code.launchpad.net

Commit message

Clarify return codes used in all the API docstrings.

Description of the change

I've not covered ValidationError yet, I want to make sure it always returns a 400.

To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

Approve. See inline comments.

review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Gosh darn I thought I had fixed those, thanks for spotting!

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (21.4 KiB)

The attempt to merge lp:~julian-edwards/maas/api-doc-bug-1391193 into lp:maas failed. Below is the output from the failed tests.

Ign http://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools gjs ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-seamicroclient python-simplejson python-simplestreams python...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api/files.py'
--- src/maasserver/api/files.py 2014-08-16 10:34:55 +0000
+++ src/maasserver/api/files.py 2014-11-24 18:23:46 +0000
@@ -184,6 +184,11 @@
184 :type filename: string184 :type filename: string
185 :param file: Actual file data with content type185 :param file: Actual file data with content type
186 application/octet-stream186 application/octet-stream
187
188 Returns 400 if any of these conditions apply:
189 - The filename is missing from the parameters
190 - The file data is missing
191 - More than one file is supplied
187 """192 """
188 filename = request.data.get("filename", None)193 filename = request.data.get("filename", None)
189 if not filename:194 if not filename:
190195
=== modified file 'src/maasserver/api/ip_addresses.py'
--- src/maasserver/api/ip_addresses.py 2014-10-22 10:35:09 +0000
+++ src/maasserver/api/ip_addresses.py 2014-11-24 18:23:46 +0000
@@ -79,6 +79,9 @@
79 :param network: CIDR representation of the network on which the IP79 :param network: CIDR representation of the network on which the IP
80 reservation is required. e.g. 10.1.2.0/2480 reservation is required. e.g. 10.1.2.0/24
81 :type network: unicode81 :type network: unicode
82
83 Returns 400 if there is no network in MAAS matching the provided one.
84 Returns 503 if there are no more IP addresses available.
82 """85 """
83 network = get_mandatory_param(request.POST, "network")86 network = get_mandatory_param(request.POST, "network")
84 requested_address = get_optional_param(87 requested_address = get_optional_param(
@@ -111,6 +114,8 @@
111114
112 :param ip: The IP address to release.115 :param ip: The IP address to release.
113 :type ip: unicode116 :type ip: unicode
117
118 Returns 404 if the provided IP address is not found.
114 """119 """
115 ip = get_mandatory_param(request.POST, "ip")120 ip = get_mandatory_param(request.POST, "ip")
116 staticaddress = get_object_or_404(121 staticaddress = get_object_or_404(
117122
=== modified file 'src/maasserver/api/node_group_interfaces.py'
--- src/maasserver/api/node_group_interfaces.py 2014-09-10 16:20:31 +0000
+++ src/maasserver/api/node_group_interfaces.py 2014-11-24 18:23:46 +0000
@@ -94,6 +94,10 @@
94 :param static_ip_range_high: Highest static IP address to assign to94 :param static_ip_range_high: Highest static IP address to assign to
95 clients.95 clients.
96 :type static_ip_range_high: unicode (IP Address)96 :type static_ip_range_high: unicode (IP Address)
97
98 Returns 404 if the node group (cluster) is not found.
99 Returns 403 if the user does not have permission to access the
100 interface.
97 """101 """
98 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)102 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
99 if not request.user.is_superuser:103 if not request.user.is_superuser:
@@ -139,7 +143,10 @@
139 return nodegroupinterface143 return nodegroupinterface
140144
141 def read(self, request, uuid, name):145 def read(self, request, uuid, name):
142 """List of NodeGroupInterfaces of a NodeGroup."""146 """List of NodeGroupInterfaces of a NodeGroup.
147
148 Returns 404 if the nodegroup (cluster) is not found.
149 """
143 # Read-only access is allowed to any user.150 # Read-only access is allowed to any user.
144 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)151 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
145 return get_object_or_404(152 return get_object_or_404(
@@ -171,6 +178,10 @@
171 :param static_ip_range_high: Highest static IP address to assign to178 :param static_ip_range_high: Highest static IP address to assign to
172 clients.179 clients.
173 :type static_ip_range_high: unicode (IP Address)180 :type static_ip_range_high: unicode (IP Address)
181
182 Returns 404 if the nodegroup (cluster) is not found.
183 Returns 403 if the user does not have permission to access the
184 interface.
174 """185 """
175 nodegroupinterface = self.get_interface(request, uuid, name)186 nodegroupinterface = self.get_interface(request, uuid, name)
176 form = NodeGroupInterfaceForm(187 form = NodeGroupInterfaceForm(
@@ -181,7 +192,12 @@
181 raise ValidationError(form.errors)192 raise ValidationError(form.errors)
182193
183 def delete(self, request, uuid, name):194 def delete(self, request, uuid, name):
184 """Delete a specific NodeGroupInterface."""195 """Delete a specific NodeGroupInterface.
196
197 Returns 404 if the nodegroup (cluster) is not found.
198 Returns 403 if the user does not have permission to access the
199 interface.
200 """
185 nodegroupinterface = self.get_interface(request, uuid, name)201 nodegroupinterface = self.get_interface(request, uuid, name)
186 nodegroupinterface.delete()202 nodegroupinterface.delete()
187 return rc.DELETED203 return rc.DELETED
188204
=== modified file 'src/maasserver/api/node_groups.py'
--- src/maasserver/api/node_groups.py 2014-10-09 20:24:04 +0000
+++ src/maasserver/api/node_groups.py 2014-11-24 18:23:46 +0000
@@ -92,7 +92,10 @@
92 :param uuid: The UUID (or list of UUIDs) of the nodegroup(s) to accept.92 :param uuid: The UUID (or list of UUIDs) of the nodegroup(s) to accept.
93 :type name: unicode (or list of unicodes)93 :type name: unicode (or list of unicodes)
9494
95 This method is reserved to admin users.95 This method is reserved to admin users and returns 403 if the
96 user is not an admin.
97
98 Returns 404 if the nodegroup (cluster) is not found.
96 """99 """
97 uuids = request.data.getlist('uuid')100 uuids = request.data.getlist('uuid')
98 for uuid in uuids:101 for uuid in uuids:
@@ -125,7 +128,10 @@
125 :param uuid: The UUID (or list of UUIDs) of the nodegroup(s) to reject.128 :param uuid: The UUID (or list of UUIDs) of the nodegroup(s) to reject.
126 :type name: unicode (or list of unicodes)129 :type name: unicode (or list of unicodes)
127130
128 This method is reserved to admin users.131 This method is reserved to admin users and returns 403 if the
132 user is not an admin.
133
134 Returns 404 if the nodegroup (cluster) is not found.
129 """135 """
130 uuids = request.data.getlist('uuid')136 uuids = request.data.getlist('uuid')
131 for uuid in uuids:137 for uuid in uuids:
@@ -173,7 +179,10 @@
173 fields = DISPLAYED_NODEGROUP_FIELDS179 fields = DISPLAYED_NODEGROUP_FIELDS
174180
175 def read(self, request, uuid):181 def read(self, request, uuid):
176 """GET a node group."""182 """GET a node group.
183
184 Returns 404 if the nodegroup (cluster) is not found.
185 """
177 return get_object_or_404(NodeGroup, uuid=uuid)186 return get_object_or_404(NodeGroup, uuid=uuid)
178187
179 @classmethod188 @classmethod
@@ -195,6 +204,8 @@
195 :param status: The new status for this cluster (see204 :param status: The new status for this cluster (see
196 vocabulary `NODEGROUP_STATUS`).205 vocabulary `NODEGROUP_STATUS`).
197 :type status: int206 :type status: int
207
208 Returns 404 if the nodegroup (cluster) is not found.
198 """209 """
199 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)210 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
200 form = NodeGroupEdit(instance=nodegroup, data=request.data)211 form = NodeGroupEdit(instance=nodegroup, data=request.data)
@@ -206,7 +217,10 @@
206 @admin_method217 @admin_method
207 @operation(idempotent=False)218 @operation(idempotent=False)
208 def import_boot_images(self, request, uuid):219 def import_boot_images(self, request, uuid):
209 """Import the pxe files on this cluster controller."""220 """Import the pxe files on this cluster controller.
221
222 Returns 404 if the nodegroup (cluster) is not found.
223 """
210 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)224 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
211 nodegroup.import_boot_images()225 nodegroup.import_boot_images()
212 return HttpResponse(226 return HttpResponse(
@@ -215,7 +229,10 @@
215229
216 @operation(idempotent=True)230 @operation(idempotent=True)
217 def list_nodes(self, request, uuid):231 def list_nodes(self, request, uuid):
218 """Get the list of node ids that are part of this group."""232 """Get the list of node ids that are part of this group.
233
234 Returns 404 if the nodegroup (cluster) is not found.
235 """
219 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)236 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
220 if not request.user.is_superuser:237 if not request.user.is_superuser:
221 check_nodegroup_access(request, nodegroup)238 check_nodegroup_access(request, nodegroup)
@@ -250,6 +267,8 @@
250 b) Requests for nodes that are not part of the nodegroup are267 b) Requests for nodes that are not part of the nodegroup are
251 just ignored.268 just ignored.
252269
270 Returns 404 if the nodegroup (cluster) is not found.
271 Returns 403 if the user does not have access to the nodegroup.
253 """272 """
254 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)273 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
255 if not request.user.is_superuser:274 if not request.user.is_superuser:
@@ -318,6 +337,10 @@
318 :param error: Optional error string. A download that has submitted an337 :param error: Optional error string. A download that has submitted an
319 error with its last progress report is considered to have failed.338 error with its last progress report is considered to have failed.
320 :type error: unicode339 :type error: unicode
340
341 Returns 404 if the nodegroup (cluster) is not found.
342 Returns 403 if the user does not have access to the nodegroup.
343 Returns 400 if the required parameters were not passed.
321 """344 """
322 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)345 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
323 check_nodegroup_access(request, nodegroup)346 check_nodegroup_access(request, nodegroup)
@@ -372,6 +395,10 @@
372 :param power_pass: The password to use, when qemu+ssh is given as a395 :param power_pass: The password to use, when qemu+ssh is given as a
373 connection string and ssh key authentication is not being used.396 connection string and ssh key authentication is not being used.
374 :type power_pass: unicode397 :type power_pass: unicode
398
399 Returns 404 if the nodegroup (cluster) is not found.
400 Returns 403 if the user does not have access to the nodegroup.
401 Returns 400 if the required parameters were not passed.
375 """402 """
376 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)403 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
377404
@@ -409,6 +436,9 @@
409 :param password: The password for the API.436 :param password: The password for the API.
410 :type password: unicode437 :type password: unicode
411438
439 Returns 404 if the nodegroup (cluster) is not found.
440 Returns 403 if the user does not have access to the nodegroup.
441 Returns 400 if the required parameters were not passed.
412 """442 """
413 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)443 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
414444
@@ -432,6 +462,9 @@
432 :param password: The password for the MSCM.462 :param password: The password for the MSCM.
433 :type password: unicode463 :type password: unicode
434464
465 Returns 404 if the nodegroup (cluster) is not found.
466 Returns 403 if the user does not have access to the nodegroup.
467 Returns 400 if the required parameters were not passed.
435 """468 """
436 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)469 nodegroup = get_object_or_404(NodeGroup, uuid=uuid)
437470
438471
=== modified file 'src/maasserver/api/node_macs.py'
--- src/maasserver/api/node_macs.py 2014-08-18 13:27:34 +0000
+++ src/maasserver/api/node_macs.py 2014-11-24 18:23:46 +0000
@@ -41,14 +41,20 @@
41 update = delete = None41 update = delete = None
4242
43 def read(self, request, system_id):43 def read(self, request, system_id):
44 """Read all MAC addresses related to a Node."""44 """Read all MAC addresses related to a Node.
45
46 Returns 404 if the node is not found.
47 """
45 node = Node.objects.get_node_or_404(48 node = Node.objects.get_node_or_404(
46 user=request.user, system_id=system_id, perm=NODE_PERMISSION.VIEW)49 user=request.user, system_id=system_id, perm=NODE_PERMISSION.VIEW)
4750
48 return MACAddress.objects.filter(node=node).order_by('id')51 return MACAddress.objects.filter(node=node).order_by('id')
4952
50 def create(self, request, system_id):53 def create(self, request, system_id):
51 """Create a MAC address for a specified Node."""54 """Create a MAC address for a specified Node.
55
56 Returns 404 if the node is not found.
57 """
52 node = Node.objects.get_node_or_404(58 node = Node.objects.get_node_or_404(
53 user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT)59 user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT)
54 mac = node.add_mac_address(request.data.get('mac_address', None))60 mac = node.add_mac_address(request.data.get('mac_address', None))
@@ -71,7 +77,10 @@
71 model = MACAddress77 model = MACAddress
7278
73 def read(self, request, system_id, mac_address):79 def read(self, request, system_id, mac_address):
74 """Read a MAC address related to a Node."""80 """Read a MAC address related to a Node.
81
82 Returns 404 if the node or the MAC address is not found.
83 """
75 node = Node.objects.get_node_or_404(84 node = Node.objects.get_node_or_404(
76 user=request.user, system_id=system_id, perm=NODE_PERMISSION.VIEW)85 user=request.user, system_id=system_id, perm=NODE_PERMISSION.VIEW)
7786
@@ -80,7 +89,11 @@
80 MACAddress, node=node, mac_address=mac_address)89 MACAddress, node=node, mac_address=mac_address)
8190
82 def delete(self, request, system_id, mac_address):91 def delete(self, request, system_id, mac_address):
83 """Delete a specific MAC address for the specified Node."""92 """Delete a specific MAC address for the specified Node.
93
94 Returns 404 if the node or the MAC address is not found.
95 Returns 204 after the MAC address is successfully deleted.
96 """
84 validate_mac(mac_address)97 validate_mac(mac_address)
85 node = Node.objects.get_node_or_404(98 node = Node.objects.get_node_or_404(
86 user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT)99 user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT)
87100
=== modified file 'src/maasserver/api/nodes.py'
--- src/maasserver/api/nodes.py 2014-11-05 15:10:12 +0000
+++ src/maasserver/api/nodes.py 2014-11-24 18:23:46 +0000
@@ -197,7 +197,10 @@
197 return node.owner.username197 return node.owner.username
198198
199 def read(self, request, system_id):199 def read(self, request, system_id):
200 """Read a specific Node."""200 """Read a specific Node.
201
202 Returns 404 if the node is not found.
203 """
201 return Node.objects.get_node_or_404(204 return Node.objects.get_node_or_404(
202 system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW)205 system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW)
203206
@@ -231,6 +234,9 @@
231 :type power_parameters_skip_check: unicode234 :type power_parameters_skip_check: unicode
232 :param zone: Name of a valid physical zone in which to place this node235 :param zone: Name of a valid physical zone in which to place this node
233 :type zone: unicode236 :type zone: unicode
237
238 Returns 404 if the node is node found.
239 Returns 403 if the user does not have permission to update the node.
234 """240 """
235 node = Node.objects.get_node_or_404(241 node = Node.objects.get_node_or_404(
236 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)242 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
@@ -243,7 +249,12 @@
243 raise ValidationError(form.errors)249 raise ValidationError(form.errors)
244250
245 def delete(self, request, system_id):251 def delete(self, request, system_id):
246 """Delete a specific Node."""252 """Delete a specific Node.
253
254 Returns 404 if the node is not found.
255 Returns 403 if the user does not have permission to delete the node.
256 Returns 204 if the node is successfully deleted.
257 """
247 node = Node.objects.get_node_or_404(258 node = Node.objects.get_node_or_404(
248 system_id=system_id, user=request.user,259 system_id=system_id, user=request.user,
249 perm=NODE_PERMISSION.ADMIN)260 perm=NODE_PERMISSION.ADMIN)
@@ -274,6 +285,9 @@
274 gracefully before powering off, while a hard power off285 gracefully before powering off, while a hard power off
275 occurs immediately without any warning to the OS.286 occurs immediately without any warning to the OS.
276 :type stop_mode: unicode287 :type stop_mode: unicode
288
289 Returns 404 if the node is not found.
290 Returns 403 if the user does not have permission to stop the node.
277 """291 """
278 stop_mode = request.POST.get('stop_mode', 'hard')292 stop_mode = request.POST.get('stop_mode', 'hard')
279 node = Node.objects.get_node_or_404(293 node = Node.objects.get_node_or_404(
@@ -300,6 +314,12 @@
300 deal with the encapsulation of binary data, but couldn't make it work314 deal with the encapsulation of binary data, but couldn't make it work
301 with the framework in reasonable time so went for a dumb, manual315 with the framework in reasonable time so went for a dumb, manual
302 encoding instead.316 encoding instead.
317
318 Returns 404 if the node is not found.
319 Returns 403 if the user does not have permission to stop the node.
320 Returns 503 if the start-up attempted to allocate an IP address,
321 and there were no IP addresses available on the relevant cluster
322 interface.
303 """323 """
304 user_data = request.POST.get('user_data', None)324 user_data = request.POST.get('user_data', None)
305 series = request.POST.get('distro_series', None)325 series = request.POST.get('distro_series', None)
@@ -335,7 +355,12 @@
335355
336 @operation(idempotent=False)356 @operation(idempotent=False)
337 def release(self, request, system_id):357 def release(self, request, system_id):
338 """Release a node. Opposite of `NodesHandler.acquire`."""358 """Release a node. Opposite of `NodesHandler.acquire`.
359
360 Returns 404 if the node is not found.
361 Returns 403 if the user does not have permission to release the node.
362 Returns 409 if the node is in a state where it may not be released.
363 """
339 node = Node.objects.get_node_or_404(364 node = Node.objects.get_node_or_404(
340 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)365 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
341 if node.status == NODE_STATUS.READY:366 if node.status == NODE_STATUS.READY:
@@ -360,6 +385,8 @@
360 already in the 'ready' state this is considered a re-commissioning385 already in the 'ready' state this is considered a re-commissioning
361 process which is useful if commissioning tests were changed after386 process which is useful if commissioning tests were changed after
362 it previously commissioned.387 it previously commissioned.
388
389 Returns 404 if the node is not found.
363 """390 """
364 node = get_object_or_404(Node, system_id=system_id)391 node = get_object_or_404(Node, system_id=system_id)
365 form_class = get_action_form(user=request.user)392 form_class = get_action_form(user=request.user)
@@ -383,6 +410,8 @@
383 Note that this is returned as BSON and not JSON. This is for410 Note that this is returned as BSON and not JSON. This is for
384 efficiency, but mainly because JSON can't do binary content411 efficiency, but mainly because JSON can't do binary content
385 without applying additional encoding like base-64.412 without applying additional encoding like base-64.
413
414 Returns 404 if the node is not found.
386 """415 """
387 node = get_object_or_404(Node, system_id=system_id)416 node = get_object_or_404(Node, system_id=system_id)
388 probe_details = get_single_probed_details(node.system_id)417 probe_details = get_single_probed_details(node.system_id)
@@ -415,6 +444,12 @@
415 an admin to give a node a stable IP, since normally an automatic444 an admin to give a node a stable IP, since normally an automatic
416 IP is allocated to a node only during the time a user has445 IP is allocated to a node only during the time a user has
417 acquired and started a node.446 acquired and started a node.
447
448 Returns 404 if the node is not found.
449 Returns 409 if the node is in an allocated state.
450 Returns 400 if the mac_address is not found on the node.
451 Returns 503 if there are not enough IPs left on the cluster interface
452 to which the mac_address is linked.
418 """453 """
419 node = get_object_or_404(Node, system_id=system_id)454 node = get_object_or_404(Node, system_id=system_id)
420 if node.status == NODE_STATUS.ALLOCATED:455 if node.status == NODE_STATUS.ALLOCATED:
@@ -449,6 +484,10 @@
449 :param error_description: An optional description of the reason the484 :param error_description: An optional description of the reason the
450 node is being marked broken.485 node is being marked broken.
451 :type error_description: unicode486 :type error_description: unicode
487
488 Returns 404 if the node is not found.
489 Returns 403 if the user does not have permission to mark the node
490 broken.
452 """491 """
453 node = Node.objects.get_node_or_404(492 node = Node.objects.get_node_or_404(
454 user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT)493 user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT)
@@ -459,7 +498,12 @@
459498
460 @operation(idempotent=False)499 @operation(idempotent=False)
461 def mark_fixed(self, request, system_id):500 def mark_fixed(self, request, system_id):
462 """Mark a broken node as fixed and set its status as 'ready'."""501 """Mark a broken node as fixed and set its status as 'ready'.
502
503 Returns 404 if the node is not found.
504 Returns 403 if the user does not have permission to mark the node
505 broken.
506 """
463 node = Node.objects.get_node_or_404(507 node = Node.objects.get_node_or_404(
464 user=request.user, system_id=system_id, perm=NODE_PERMISSION.ADMIN)508 user=request.user, system_id=system_id, perm=NODE_PERMISSION.ADMIN)
465 node.mark_fixed()509 node.mark_fixed()
@@ -473,11 +517,14 @@
473 def power_parameters(self, request, system_id):517 def power_parameters(self, request, system_id):
474 """Obtain power parameters.518 """Obtain power parameters.
475519
476 This method is reserved for admin users.520 This method is reserved for admin users and returns a 403 if the
521 user is not one.
477522
478 This returns the power parameters, if any, configured for a523 This returns the power parameters, if any, configured for a
479 node. For some types of power control this will include private524 node. For some types of power control this will include private
480 information such as passwords and secret keys.525 information such as passwords and secret keys.
526
527 Returns 404 if the node is not found.
481 """528 """
482 node = get_object_or_404(Node, system_id=system_id)529 node = get_object_or_404(Node, system_id=system_id)
483 return node.power_parameters530 return node.power_parameters
@@ -492,12 +539,13 @@
492 Use this method sparingly as it ties up an appserver thread539 Use this method sparingly as it ties up an appserver thread
493 while waiting.540 while waiting.
494541
495 If there is a problem, SERVICE_UNAVAILABLE is returned with text
496 explaining why.
497
498 :param system_id: The node to query.542 :param system_id: The node to query.
499 :return: a dict whose key is "state" with a value of one of543 :return: a dict whose key is "state" with a value of one of
500 'on' or 'off'.544 'on' or 'off'.
545
546 Returns 404 if the node is not found.
547 Returns 503 (with explanatory text) if the power state could not
548 be queried.
501 """549 """
502 node = get_object_or_404(Node, system_id=system_id)550 node = get_object_or_404(Node, system_id=system_id)
503 ng = node.nodegroup551 ng = node.nodegroup
@@ -540,6 +588,10 @@
540 """Abort a node's current operation.588 """Abort a node's current operation.
541589
542 This currently only supports aborting of the 'Disk Erasing' operation.590 This currently only supports aborting of the 'Disk Erasing' operation.
591
592 Returns 404 if the node could not be found.
593 Returns 403 if the user does not have permission to abort the
594 current operation.
543 """595 """
544 node = Node.objects.get_node_or_404(596 node = Node.objects.get_node_or_404(
545 system_id=system_id, user=request.user,597 system_id=system_id, user=request.user,
@@ -648,6 +700,8 @@
648 :type mac_address: unicode700 :type mac_address: unicode
649 :return: 'true' or 'false'.701 :return: 'true' or 'false'.
650 :rtype: unicode702 :rtype: unicode
703
704 Returns 400 if any mandatory parameters are missing.
651 """705 """
652 mac_address = get_mandatory_param(request.GET, 'mac_address')706 mac_address = get_mandatory_param(request.GET, 'mac_address')
653 mac_addresses = MACAddress.objects.filter(mac_address=mac_address)707 mac_addresses = MACAddress.objects.filter(mac_address=mac_address)
@@ -656,7 +710,10 @@
656710
657 @operation(idempotent=False)711 @operation(idempotent=False)
658 def accept(self, request):712 def accept(self, request):
659 """Accept a node's enlistment: not allowed to anonymous users."""713 """Accept a node's enlistment: not allowed to anonymous users.
714
715 Always returns 401.
716 """
660 raise Unauthorized("You must be logged in to accept nodes.")717 raise Unauthorized("You must be logged in to accept nodes.")
661718
662 @classmethod719 @classmethod
@@ -726,6 +783,9 @@
726 :return: The system_ids of any nodes that have their status changed783 :return: The system_ids of any nodes that have their status changed
727 by this call. Thus, nodes that were already accepted are784 by this call. Thus, nodes that were already accepted are
728 excluded from the result.785 excluded from the result.
786
787 Returns 400 if any of the nodes do not exist.
788 Returns 403 if the user is not an admin.
729 """789 """
730 system_ids = set(request.POST.getlist('nodes'))790 system_ids = set(request.POST.getlist('nodes'))
731 # Check the existence of these nodes first.791 # Check the existence of these nodes first.
@@ -813,6 +873,12 @@
813 :return: The system_ids of any nodes that have their status873 :return: The system_ids of any nodes that have their status
814 changed by this call. Thus, nodes that were already released874 changed by this call. Thus, nodes that were already released
815 are excluded from the result.875 are excluded from the result.
876
877 Returns 400 if any of the nodes cannot be found.
878 Returns 403 if the user does not have permission to release any of
879 the nodes.
880 Returns a 409 if any of the nodes could not be released due to their
881 current state.
816 """882 """
817 system_ids = set(request.POST.getlist('nodes'))883 system_ids = set(request.POST.getlist('nodes'))
818 # Check the existence of these nodes first.884 # Check the existence of these nodes first.
@@ -964,6 +1030,9 @@
964 :param agent_name: An optional agent name to attach to the1030 :param agent_name: An optional agent name to attach to the
965 acquired node.1031 acquired node.
966 :type agent_name: unicode1032 :type agent_name: unicode
1033
1034 Returns 409 if a suitable node matching the constraints could not be
1035 found.
967 """1036 """
968 form = AcquireNodeForm(data=request.data)1037 form = AcquireNodeForm(data=request.data)
969 maaslog.info(1038 maaslog.info(
@@ -1006,6 +1075,8 @@
1006 will be taken out of their physical zones.1075 will be taken out of their physical zones.
1007 :param nodes: system_ids of the nodes whose zones are to be set.1076 :param nodes: system_ids of the nodes whose zones are to be set.
1008 (An empty list is acceptable).1077 (An empty list is acceptable).
1078
1079 Raises 403 if the user is not an admin.
1009 """1080 """
1010 data = {1081 data = {
1011 'action': 'set_zone',1082 'action': 'set_zone',
@@ -1027,6 +1098,8 @@
1027 :type id: iterable1098 :type id: iterable
10281099
1029 :return: A dictionary of power parameters, keyed by node system_id.1100 :return: A dictionary of power parameters, keyed by node system_id.
1101
1102 Raises 403 if the user is not an admin.
1030 """1103 """
1031 match_ids = get_optional_list(request.GET, 'id')1104 match_ids = get_optional_list(request.GET, 'id')
10321105
@@ -1043,6 +1116,9 @@
10431116
1044 :param nodes: Mandatory list of system IDs for nodes whose status1117 :param nodes: Mandatory list of system IDs for nodes whose status
1045 you wish to check.1118 you wish to check.
1119
1120 Returns 400 if mandatory parameters are missing.
1121 Returns 403 if the user has no permission to view any of the nodes.
1046 """1122 """
1047 system_ids = set(request.GET.getlist('nodes'))1123 system_ids = set(request.GET.getlist('nodes'))
1048 # Check the existence of these nodes first.1124 # Check the existence of these nodes first.
10491125
=== modified file 'src/maasserver/api/ssh_keys.py'
--- src/maasserver/api/ssh_keys.py 2014-08-16 15:05:54 +0000
+++ src/maasserver/api/ssh_keys.py 2014-11-24 18:23:46 +0000
@@ -83,13 +83,20 @@
83 create = update = None83 create = update = None
8484
85 def read(self, request, keyid):85 def read(self, request, keyid):
86 """GET an SSH key."""86 """GET an SSH key.
87
88 Returns 404 if the key does not exist.
89 """
87 key = get_object_or_404(SSHKey, id=keyid)90 key = get_object_or_404(SSHKey, id=keyid)
88 return key91 return key
8992
90 @operation(idempotent=False)93 @operation(idempotent=False)
91 def delete(self, request, keyid):94 def delete(self, request, keyid):
92 """DELETE an SSH key."""95 """DELETE an SSH key.
96
97 Returns 404 if the key does not exist.
98 Returns 401 if the key does not belong to the calling user.
99 """
93 key = get_object_or_404(SSHKey, id=keyid)100 key = get_object_or_404(SSHKey, id=keyid)
94 if key.user != request.user:101 if key.user != request.user:
95 return HttpResponse(102 return HttpResponse(
96103
=== modified file 'src/maasserver/api/ssl_keys.py'
--- src/maasserver/api/ssl_keys.py 2014-08-17 01:01:12 +0000
+++ src/maasserver/api/ssl_keys.py 2014-11-24 18:23:46 +0000
@@ -83,7 +83,11 @@
83 create = update = None83 create = update = None
8484
85 def read(self, request, keyid):85 def read(self, request, keyid):
86 """GET an SSL key."""86 """GET an SSL key.
87
88 Returns 404 if the keyid is not found.
89 Returns 401 if the key does not belong to the requesting user.
90 """
87 key = get_object_or_404(SSLKey, id=keyid)91 key = get_object_or_404(SSLKey, id=keyid)
88 if key.user != request.user:92 if key.user != request.user:
89 return HttpResponse(93 return HttpResponse(
@@ -92,7 +96,11 @@
9296
93 @operation(idempotent=True)97 @operation(idempotent=True)
94 def delete(self, request, keyid):98 def delete(self, request, keyid):
95 """DELETE an SSL key."""99 """DELETE an SSL key.
100
101 Returns 401 if the key does not belong to the requesting user.
102 Returns 204 if the key is successfully deleted.
103 """
96 key = get_object_or_404(SSLKey, id=keyid)104 key = get_object_or_404(SSLKey, id=keyid)
97 if key.user != request.user:105 if key.user != request.user:
98 return HttpResponse(106 return HttpResponse(
99107
=== modified file 'src/maasserver/api/tags.py'
--- src/maasserver/api/tags.py 2014-08-18 11:43:18 +0000
+++ src/maasserver/api/tags.py 2014-11-24 18:23:46 +0000
@@ -62,7 +62,10 @@
62 )62 )
6363
64 def read(self, request, name):64 def read(self, request, name):
65 """Read a specific Tag"""65 """Read a specific Tag.
66
67 Returns 404 if the tag is not found.
68 """
66 return Tag.objects.get_tag_or_404(name=name, user=request.user)69 return Tag.objects.get_tag_or_404(name=name, user=request.user)
6770
68 def update(self, request, name):71 def update(self, request, name):
@@ -74,6 +77,8 @@
74 It is meant as a human readable description of the tag.77 It is meant as a human readable description of the tag.
75 :param definition: An XPATH query that will be evaluated against the78 :param definition: An XPATH query that will be evaluated against the
76 hardware_details stored for all nodes (output of `lshw -xml`).79 hardware_details stored for all nodes (output of `lshw -xml`).
80
81 Returns 404 if the tag is not found.
77 """82 """
78 tag = Tag.objects.get_tag_or_404(83 tag = Tag.objects.get_tag_or_404(
79 name=name, user=request.user, to_edit=True)84 name=name, user=request.user, to_edit=True)
@@ -90,7 +95,11 @@
90 raise ValidationError(form.errors)95 raise ValidationError(form.errors)
9196
92 def delete(self, request, name):97 def delete(self, request, name):
93 """Delete a specific Tag."""98 """Delete a specific Tag.
99
100 Returns 404 if the tag is not found.
101 Returns 204 if the tag is successfully deleted.
102 """
94 tag = Tag.objects.get_tag_or_404(103 tag = Tag.objects.get_tag_or_404(
95 name=name, user=request.user, to_edit=True)104 name=name, user=request.user, to_edit=True)
96 tag.delete()105 tag.delete()
@@ -98,7 +107,10 @@
98107
99 @operation(idempotent=True)108 @operation(idempotent=True)
100 def nodes(self, request, name):109 def nodes(self, request, name):
101 """Get the list of nodes that have this tag."""110 """Get the list of nodes that have this tag.
111
112 Returns 404 if the tag is not found.
113 """
102 tag = Tag.objects.get_tag_or_404(name=name, user=request.user)114 tag = Tag.objects.get_tag_or_404(name=name, user=request.user)
103 return Node.objects.get_nodes(115 return Node.objects.get_nodes(
104 request.user, NODE_PERMISSION.VIEW, from_nodes=tag.node_set.all())116 request.user, NODE_PERMISSION.VIEW, from_nodes=tag.node_set.all())
@@ -120,6 +132,8 @@
120 This is considered a maintenance operation, which should normally not132 This is considered a maintenance operation, which should normally not
121 be necessary. Adding nodes or updating a tag's definition should133 be necessary. Adding nodes or updating a tag's definition should
122 automatically trigger the appropriate changes.134 automatically trigger the appropriate changes.
135
136 Returns 404 if the tag is not found.
123 """137 """
124 tag = Tag.objects.get_tag_or_404(name=name, user=request.user,138 tag = Tag.objects.get_tag_or_404(name=name, user=request.user,
125 to_edit=True)139 to_edit=True)
@@ -141,6 +155,10 @@
141 supplied, then the requester must be the worker associated with155 supplied, then the requester must be the worker associated with
142 that nodegroup, and only nodes that are part of that nodegroup can156 that nodegroup, and only nodes that are part of that nodegroup can
143 be updated.157 be updated.
158
159 Returns 404 if the tag is not found.
160 Returns 401 if the user does not have permission to update the nodes.
161 Returns 409 if 'definition' doesn't match the current definition.
144 """162 """
145 tag = Tag.objects.get_tag_or_404(name=name, user=request.user)163 tag = Tag.objects.get_tag_or_404(name=name, user=request.user)
146 nodegroup = None164 nodegroup = None
@@ -196,6 +214,8 @@
196 value overrides the global 'kernel_opts' setting. If more than one214 value overrides the global 'kernel_opts' setting. If more than one
197 tag is associated with a node, the one with the lowest alphabetical215 tag is associated with a node, the one with the lowest alphabetical
198 name will be picked (eg 01-my-tag will be taken over 99-tag-name).216 name will be picked (eg 01-my-tag will be taken over 99-tag-name).
217
218 Returns 401 if the user is not an admin.
199 """219 """
200 if not request.user.is_superuser:220 if not request.user.is_superuser:
201 raise PermissionDenied()221 raise PermissionDenied()
202222
=== modified file 'src/maasserver/api/users.py'
--- src/maasserver/api/users.py 2014-08-16 15:14:46 +0000
+++ src/maasserver/api/users.py 2014-11-24 18:23:46 +0000
@@ -59,6 +59,8 @@
59 :type password: unicode59 :type password: unicode
60 :param is_superuser: Whether the new user is to be an administrator.60 :param is_superuser: Whether the new user is to be an administrator.
61 :type is_superuser: bool ('0' for False, '1' for True)61 :type is_superuser: bool ('0' for False, '1' for True)
62
63 Returns 400 if any mandatory parameters are missing.
62 """64 """
63 username = get_mandatory_param(request.data, 'username')65 username = get_mandatory_param(request.data, 'username')
64 email = get_mandatory_param(request.data, 'email')66 email = get_mandatory_param(request.data, 'email')
6567
=== modified file 'src/maasserver/api/zones.py'
--- src/maasserver/api/zones.py 2014-08-16 14:54:27 +0000
+++ src/maasserver/api/zones.py 2014-11-24 18:23:46 +0000
@@ -48,12 +48,18 @@
48 create = None48 create = None
4949
50 def read(self, request, name):50 def read(self, request, name):
51 """GET request. Return zone."""51 """GET request. Return zone.
52
53 Returns 404 if the zone is not found.
54 """
52 return get_object_or_404(Zone, name=name)55 return get_object_or_404(Zone, name=name)
5356
54 @admin_method57 @admin_method
55 def update(self, request, name):58 def update(self, request, name):
56 """PUT request. Update zone."""59 """PUT request. Update zone.
60
61 Returns 404 if the zone is not found.
62 """
57 zone = get_object_or_404(Zone, name=name)63 zone = get_object_or_404(Zone, name=name)
58 form = ZoneForm(instance=zone, data=request.data)64 form = ZoneForm(instance=zone, data=request.data)
59 if not form.is_valid():65 if not form.is_valid():
@@ -62,7 +68,11 @@
6268
63 @admin_method69 @admin_method
64 def delete(self, request, name):70 def delete(self, request, name):
65 """DELETE request. Delete zone."""71 """DELETE request. Delete zone.
72
73 Returns 404 if the zone is not found.
74 Returns 204 if the zone is successfully deleted.
75 """
66 zone = get_one(Zone.objects.filter(name=name))76 zone = get_one(Zone.objects.filter(name=name))
67 if zone is not None:77 if zone is not None:
68 zone.delete()78 zone.delete()