Merge lp:~blake-rouse/maas/fix-1522898-1.9 into lp:maas/1.9

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4525
Proposed branch: lp:~blake-rouse/maas/fix-1522898-1.9
Merge into: lp:maas/1.9
Diff against target: 1023 lines (+375/-94)
7 files modified
docs/changelog.rst (+2/-1)
src/maasserver/api/interfaces.py (+73/-34)
src/maasserver/api/tests/test_interfaces.py (+219/-51)
src/maasserver/forms_interface_link.py (+21/-7)
src/maasserver/models/__init__.py (+20/-1)
src/maasserver/tests/test_auth.py (+28/-0)
src/maasserver/urls_api.py (+12/-0)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1522898-1.9
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Mike Pontillo (community) Needs Information
Review via email: mp+279660@code.launchpad.net

Commit message

Fix the node interfaces API to work for devices as well. Keep node-interface(s) on the cli for compatability.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good. I've been testing it on my local MAAS 1.9 setup, and it seems solid.

Two concerns that I'd like peoples' thoughts on before we land this:

(1) It looks like the resource_uri still refers to node-interfaces, even for devices. See:

http://paste.ubuntu.com/13684305/

(2) I'm concerned that if we migrate MAC addresses to 1.8 and they become "unknown" interfaces not associated with a node, the user will have no way to delete these via the CLI/API, since we require the system_id.

review: Needs Information
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Oh, note that in the pastebin output above, I'm referring to an interface on a *device*, not a node. (at least, from the user's perspective.)

Revision history for this message
Mike Pontillo (mpontillo) wrote :

And I meant "migrate plain MAC addresses from 1.8 to 1.9".

/me needs to learn to proofread; sorry.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

> Looks good. I've been testing it on my local MAAS 1.9 setup, and it seems
> solid.
>
> Two concerns that I'd like peoples' thoughts on before we land this:
>
> (1) It looks like the resource_uri still refers to node-interfaces, even for
> devices. See:
>
> http://paste.ubuntu.com/13684305/

Good catch! I have fixed this. Miss-typed the url pattern.

>
> (2) I'm concerned that if we migrate MAC addresses to 1.8 and they become
> "unknown" interfaces not associated with a node, the user will have no way to
> delete these via the CLI/API, since we require the system_id.

This branch was never design to address that issue. This branch was just to re-name an endpoint. The only interfaces that should be unknown are those that are user-reserved. So if those are not deleted when the user-reserved IP address is deleted then there is a problem in the model logic of MAAS not at the API level. If that is a problem please file a different bug.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm! After this landing please double check that this doesn't cause the issue that Mike is talking about, because if so, then we need to fix it asap, or prevent this branch from being released.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Again this does not affect anything that Mike was talking about. Mike is just talking about an issue that we have no proof for and more of an issue of the IP address endpoint, not by me. By saying this branch causes that issue is completely wrong.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'docs/changelog.rst'
--- docs/changelog.rst 2015-12-06 16:11:01 +0000
+++ docs/changelog.rst 2015-12-07 14:29:30 +0000
@@ -9,6 +9,8 @@
9Major bugs fixed in this release9Major bugs fixed in this release
10--------------------------------10--------------------------------
1111
12LP: #1522898 "node-interface" API should just be "interface" - to allow devices to use it
13
12LP: #1519527 Juju 1.25.1 proposed: lxc units all have the same IP address after upgrade from 1.7/1.8.14LP: #1519527 Juju 1.25.1 proposed: lxc units all have the same IP address after upgrade from 1.7/1.8.
1315
14LP: #1522294 MAAS fails to parse some DHCP leases.16LP: #1522294 MAAS fails to parse some DHCP leases.
@@ -2398,4 +2400,3 @@
2398#1185897 expose ability to re-commission node in api and cli2400#1185897 expose ability to re-commission node in api and cli
23992401
2400#997092 Can't delete allocated node even if owned by self2402#997092 Can't delete allocated node even if owned by self
2401
24022403
=== modified file 'src/maasserver/api/interfaces.py'
--- src/maasserver/api/interfaces.py 2015-11-09 19:39:43 +0000
+++ src/maasserver/api/interfaces.py 2015-12-07 14:29:30 +0000
@@ -18,6 +18,7 @@
18 OperationsHandler,18 OperationsHandler,
19)19)
20from maasserver.enum import (20from maasserver.enum import (
21 INTERFACE_LINK_TYPE,
21 INTERFACE_TYPE,22 INTERFACE_TYPE,
22 NODE_PERMISSION,23 NODE_PERMISSION,
23 NODE_STATUS,24 NODE_STATUS,
@@ -75,29 +76,29 @@
75 "Cannot %s interface because the node is not Ready." % operation)76 "Cannot %s interface because the node is not Ready." % operation)
7677
7778
78class NodeInterfacesHandler(OperationsHandler):79class InterfacesHandler(OperationsHandler):
79 """Manage interfaces on a node."""80 """Manage interfaces on a node or device."""
80 api_doc_section_name = "Node Interfaces"81 api_doc_section_name = "Interfaces"
81 create = update = delete = None82 create = update = delete = None
82 fields = DISPLAYED_INTERFACE_FIELDS83 fields = DISPLAYED_INTERFACE_FIELDS
8384
84 @classmethod85 @classmethod
85 def resource_uri(cls, *args, **kwargs):86 def resource_uri(cls, *args, **kwargs):
86 # See the comment in NodeHandler.resource_uri.87 # See the comment in NodeHandler.resource_uri.
87 return ('node_interfaces_handler', ["system_id"])88 return ('interfaces_handler', ["system_id"])
8889
89 def read(self, request, system_id):90 def read(self, request, system_id):
90 """List all interfaces belonging to node.91 """List all interfaces belonging to a node or device.
9192
92 Returns 404 if the node is not found.93 Returns 404 if the node is not found.
93 """94 """
94 node = Node.nodes.get_node_or_404(95 node = Node.objects.get_node_or_404(
95 system_id, request.user, NODE_PERMISSION.VIEW)96 system_id, request.user, NODE_PERMISSION.VIEW)
96 return node.interface_set.all()97 return node.interface_set.all()
9798
98 @operation(idempotent=False)99 @operation(idempotent=False)
99 def create_physical(self, request, system_id):100 def create_physical(self, request, system_id):
100 """Create a physical interface on node.101 """Create a physical interface on a node or device.
101102
102 :param name: Name of the interface.103 :param name: Name of the interface.
103 :param mac_address: MAC address of the interface.104 :param mac_address: MAC address of the interface.
@@ -112,10 +113,12 @@
112113
113 Returns 404 if the node is not found.114 Returns 404 if the node is not found.
114 """115 """
115 node = Node.nodes.get_node_or_404(116 node = Node.objects.get_node_or_404(
116 system_id, request.user, NODE_PERMISSION.ADMIN)117 system_id, request.user, NODE_PERMISSION.EDIT)
117 raise_error_for_invalid_state_on_allocated_operations(118 # Installable nodes require the node needs to be in the correct state.
118 node, request.user, "create")119 if node.installable:
120 raise_error_for_invalid_state_on_allocated_operations(
121 node, request.user, "create")
119 form = PhysicalInterfaceForm(node=node, data=request.data)122 form = PhysicalInterfaceForm(node=node, data=request.data)
120 if form.is_valid():123 if form.is_valid():
121 return form.save()124 return form.save()
@@ -237,9 +240,9 @@
237 raise MAASAPIValidationError(form.errors)240 raise MAASAPIValidationError(form.errors)
238241
239242
240class NodeInterfaceHandler(OperationsHandler):243class InterfaceHandler(OperationsHandler):
241 """Manage a node's interface."""244 """Manage a node's or device's interface."""
242 api_doc_section_name = "Node Interface"245 api_doc_section_name = "Interface"
243 create = None246 create = None
244 model = Interface247 model = Interface
245 fields = DISPLAYED_INTERFACE_FIELDS248 fields = DISPLAYED_INTERFACE_FIELDS
@@ -254,7 +257,7 @@
254 node = interface.get_node()257 node = interface.get_node()
255 if node is not None:258 if node is not None:
256 system_id = node.system_id259 system_id = node.system_id
257 return ('node_interface_handler', (system_id, interface_id))260 return ('interface_handler', (system_id, interface_id))
258261
259 @classmethod262 @classmethod
260 def mac_address(cls, interface):263 def mac_address(cls, interface):
@@ -372,9 +375,12 @@
372 Returns 404 if the node or interface is not found.375 Returns 404 if the node or interface is not found.
373 """376 """
374 interface = Interface.objects.get_interface_or_404(377 interface = Interface.objects.get_interface_or_404(
375 system_id, interface_id, request.user, NODE_PERMISSION.ADMIN)378 system_id, interface_id, request.user, NODE_PERMISSION.EDIT)
376 raise_error_for_invalid_state_on_allocated_operations(379 if interface.get_node().installable:
377 interface.node, request.user, "update interface")380 # This node needs to be in the correct state to modify
381 # the interface.
382 raise_error_for_invalid_state_on_allocated_operations(
383 interface.node, request.user, "update interface")
378 interface_form = InterfaceForm.get_interface_form(interface.type)384 interface_form = InterfaceForm.get_interface_form(interface.type)
379 # For VLAN interface we cast parents to parent. As a VLAN can only385 # For VLAN interface we cast parents to parent. As a VLAN can only
380 # have one parent.386 # have one parent.
@@ -399,9 +405,12 @@
399 Returns 404 if the node or interface is not found.405 Returns 404 if the node or interface is not found.
400 """406 """
401 interface = Interface.objects.get_interface_or_404(407 interface = Interface.objects.get_interface_or_404(
402 system_id, interface_id, request.user, NODE_PERMISSION.ADMIN)408 system_id, interface_id, request.user, NODE_PERMISSION.EDIT)
403 raise_error_for_invalid_state_on_allocated_operations(409 if interface.get_node().installable:
404 interface.node, request.user, "delete interface")410 # This node needs to be in the correct state to modify
411 # the interface.
412 raise_error_for_invalid_state_on_allocated_operations(
413 interface.node, request.user, "delete interface")
405 interface.delete()414 interface.delete()
406 return rc.DELETED415 return rc.DELETED
407416
@@ -437,10 +446,24 @@
437 Returns 404 if the node or interface is not found.446 Returns 404 if the node or interface is not found.
438 """447 """
439 interface = Interface.objects.get_interface_or_404(448 interface = Interface.objects.get_interface_or_404(
440 system_id, interface_id, request.user, NODE_PERMISSION.ADMIN)449 system_id, interface_id, request.user, NODE_PERMISSION.EDIT)
441 raise_error_for_invalid_state_on_allocated_operations(450 node = interface.get_node()
442 interface.node, request.user, "link subnet")451 if node.installable:
443 form = InterfaceLinkForm(instance=interface, data=request.data)452 # This node needs to be in the correct state to modify
453 # the interface.
454 raise_error_for_invalid_state_on_allocated_operations(
455 interface.node, request.user, "link subnet")
456 allowed_modes = [
457 INTERFACE_LINK_TYPE.AUTO,
458 INTERFACE_LINK_TYPE.DHCP,
459 INTERFACE_LINK_TYPE.STATIC,
460 INTERFACE_LINK_TYPE.LINK_UP,
461 ]
462 else:
463 # Devices can only be set in static IP mode.
464 allowed_modes = [INTERFACE_LINK_TYPE.STATIC]
465 form = InterfaceLinkForm(
466 instance=interface, data=request.data, allowed_modes=allowed_modes)
444 if form.is_valid():467 if form.is_valid():
445 return form.save()468 return form.save()
446 else:469 else:
@@ -455,9 +478,12 @@
455 Returns 404 if the node or interface is not found.478 Returns 404 if the node or interface is not found.
456 """479 """
457 interface = Interface.objects.get_interface_or_404(480 interface = Interface.objects.get_interface_or_404(
458 system_id, interface_id, request.user, NODE_PERMISSION.ADMIN)481 system_id, interface_id, request.user, NODE_PERMISSION.EDIT)
459 raise_error_for_invalid_state_on_allocated_operations(482 if interface.get_node().installable:
460 interface.node, request.user, "unlink subnet")483 # This node needs to be in the correct state to modify
484 # the interface.
485 raise_error_for_invalid_state_on_allocated_operations(
486 interface.node, request.user, "unlink subnet")
461 form = InterfaceUnlinkForm(instance=interface, data=request.data)487 form = InterfaceUnlinkForm(instance=interface, data=request.data)
462 if form.is_valid():488 if form.is_valid():
463 return form.save()489 return form.save()
@@ -479,9 +505,12 @@
479 Returns 404 if the node or interface is not found.505 Returns 404 if the node or interface is not found.
480 """506 """
481 interface = Interface.objects.get_interface_or_404(507 interface = Interface.objects.get_interface_or_404(
482 system_id, interface_id, request.user, NODE_PERMISSION.ADMIN)508 system_id, interface_id, request.user, NODE_PERMISSION.EDIT)
483 raise_error_for_invalid_state_on_allocated_operations(509 if interface.get_node().installable:
484 interface.node, request.user, "set default gateway")510 # This node needs to be in the correct state to modify
511 # the interface.
512 raise_error_for_invalid_state_on_allocated_operations(
513 interface.node, request.user, "set default gateway")
485 form = InterfaceSetDefaultGatwayForm(514 form = InterfaceSetDefaultGatwayForm(
486 instance=interface, data=request.data)515 instance=interface, data=request.data)
487 if form.is_valid():516 if form.is_valid():
@@ -490,7 +519,17 @@
490 raise MAASAPIValidationError(form.errors)519 raise MAASAPIValidationError(form.errors)
491520
492521
493class PhysicalInterfaceHandler(NodeInterfaceHandler):522class NodeInterfacesHandler(InterfacesHandler):
523 """Manage interfaces on a node. (Deprecated)"""
524 api_doc_section_name = "Node Interfaces"
525
526
527class NodeInterfaceHandler(InterfaceHandler):
528 """Manage a node's interface. (Deprecated)"""
529 api_doc_section_name = "Node Interface"
530
531
532class PhysicaInterfaceHandler(InterfaceHandler):
494 """533 """
495 This handler only exists because piston requires a unique handler per534 This handler only exists because piston requires a unique handler per
496 class type. Without this class the resource_uri will not be added to any535 class type. Without this class the resource_uri will not be added to any
@@ -504,7 +543,7 @@
504 model = PhysicalInterface543 model = PhysicalInterface
505544
506545
507class BondInterfaceHandler(NodeInterfaceHandler):546class BondInterfaceHandler(InterfaceHandler):
508 """547 """
509 This handler only exists because piston requires a unique handler per548 This handler only exists because piston requires a unique handler per
510 class type. Without this class the resource_uri will not be added to any549 class type. Without this class the resource_uri will not be added to any
@@ -518,7 +557,7 @@
518 model = BondInterface557 model = BondInterface
519558
520559
521class VLANInterfaceHandler(NodeInterfaceHandler):560class VLANInterfaceHandler(InterfaceHandler):
522 """561 """
523 This handler only exists because piston requires a unique handler per562 This handler only exists because piston requires a unique handler per
524 class type. Without this class the resource_uri will not be added to any563 class type. Without this class the resource_uri will not be added to any
525564
=== modified file 'src/maasserver/api/tests/test_interfaces.py'
--- src/maasserver/api/tests/test_interfaces.py 2015-11-10 17:50:31 +0000
+++ src/maasserver/api/tests/test_interfaces.py 2015-12-07 14:29:30 +0000
@@ -37,20 +37,20 @@
37)37)
3838
3939
40def get_node_interfaces_uri(node):40def get_interfaces_uri(node):
41 """Return a Node's interfaces URI on the API."""41 """Return a interfaces URI on the API."""
42 return reverse(42 return reverse(
43 'node_interfaces_handler', args=[node.system_id])43 'interfaces_handler', args=[node.system_id])
4444
4545
46def get_node_interface_uri(interface, node=None):46def get_interface_uri(interface, node=None):
47 """Return a Node's interface URI on the API."""47 """Return a interface URI on the API."""
48 if isinstance(interface, Interface):48 if isinstance(interface, Interface):
49 if node is None:49 if node is None:
50 node = interface.get_node()50 node = interface.get_node()
51 interface = interface.id51 interface = interface.id
52 return reverse(52 return reverse(
53 'node_interface_handler', args=[node.system_id, interface])53 'interface_handler', args=[node.system_id, interface])
5454
5555
56def make_complex_interface(node, name=None):56def make_complex_interface(node, name=None):
@@ -74,18 +74,18 @@
74 return bond_interface, parents, [vlan_nic_10, vlan_nic_11]74 return bond_interface, parents, [vlan_nic_10, vlan_nic_11]
7575
7676
77class TestNodeInterfacesAPI(APITestCase):77class TestInterfacesAPI(APITestCase):
7878
79 def test_handler_path(self):79 def test_handler_path(self):
80 node = factory.make_Node()80 node = factory.make_Node()
81 self.assertEqual(81 self.assertEqual(
82 '/api/1.0/nodes/%s/interfaces/' % (node.system_id),82 '/api/1.0/nodes/%s/interfaces/' % (node.system_id),
83 get_node_interfaces_uri(node))83 get_interfaces_uri(node))
8484
85 def test_read(self):85 def test_read(self):
86 node = factory.make_Node()86 node = factory.make_Node()
87 bond, parents, children = make_complex_interface(node)87 bond, parents, children = make_complex_interface(node)
88 uri = get_node_interfaces_uri(node)88 uri = get_interfaces_uri(node)
89 response = self.client.get(uri)89 response = self.client.get(uri)
9090
91 self.assertEqual(httplib.OK, response.status_code, response.content)91 self.assertEqual(httplib.OK, response.status_code, response.content)
@@ -99,6 +99,18 @@
99 ]99 ]
100 self.assertItemsEqual(expected_ids, result_ids)100 self.assertItemsEqual(expected_ids, result_ids)
101101
102 def test_read_on_device(self):
103 parent = factory.make_Node()
104 device = factory.make_Node(
105 owner=self.logged_in_user, installable=False, parent=parent)
106 interface = factory.make_Interface(
107 INTERFACE_TYPE.PHYSICAL, node=device)
108 uri = get_interfaces_uri(device)
109 response = self.client.get(uri)
110
111 self.assertEqual(httplib.OK, response.status_code, response.content)
112 self.assertEqual(interface.id, json.loads(response.content)[0]['id'])
113
102 def test_create_physical(self):114 def test_create_physical(self):
103 self.become_admin()115 self.become_admin()
104 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):116 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
@@ -110,7 +122,7 @@
110 factory.make_name("tag")122 factory.make_name("tag")
111 for _ in range(3)123 for _ in range(3)
112 ]124 ]
113 uri = get_node_interfaces_uri(node)125 uri = get_interfaces_uri(node)
114 response = self.client.post(uri, {126 response = self.client.post(uri, {
115 "op": "create_physical",127 "op": "create_physical",
116 "mac_address": mac,128 "mac_address": mac,
@@ -132,6 +144,39 @@
132 "enabled": Equals(True),144 "enabled": Equals(True),
133 }))145 }))
134146
147 def test_create_physical_on_device(self):
148 parent = factory.make_Node()
149 device = factory.make_Node(
150 owner=self.logged_in_user, installable=False, parent=parent)
151 mac = factory.make_mac_address()
152 name = factory.make_name("eth")
153 vlan = factory.make_VLAN()
154 tags = [
155 factory.make_name("tag")
156 for _ in range(3)
157 ]
158 uri = get_interfaces_uri(device)
159 response = self.client.post(uri, {
160 "op": "create_physical",
161 "mac_address": mac,
162 "name": name,
163 "vlan": vlan.id,
164 "tags": ",".join(tags),
165 })
166
167 self.assertEqual(
168 httplib.OK, response.status_code, response.content)
169 self.assertThat(json.loads(response.content), ContainsDict({
170 "mac_address": Equals(mac),
171 "name": Equals(name),
172 "vlan": ContainsDict({
173 "id": Equals(vlan.id),
174 }),
175 "type": Equals("physical"),
176 "tags": Equals(tags),
177 "enabled": Equals(True),
178 }))
179
135 def test_create_physical_disabled(self):180 def test_create_physical_disabled(self):
136 self.become_admin()181 self.become_admin()
137 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):182 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
@@ -143,7 +188,7 @@
143 factory.make_name("tag")188 factory.make_name("tag")
144 for _ in range(3)189 for _ in range(3)
145 ]190 ]
146 uri = get_node_interfaces_uri(node)191 uri = get_interfaces_uri(node)
147 response = self.client.post(uri, {192 response = self.client.post(uri, {
148 "op": "create_physical",193 "op": "create_physical",
149 "mac_address": mac,194 "mac_address": mac,
@@ -171,7 +216,7 @@
171 mac = factory.make_mac_address()216 mac = factory.make_mac_address()
172 name = factory.make_name("eth")217 name = factory.make_name("eth")
173 vlan = factory.make_VLAN()218 vlan = factory.make_VLAN()
174 uri = get_node_interfaces_uri(node)219 uri = get_interfaces_uri(node)
175 response = self.client.post(uri, {220 response = self.client.post(uri, {
176 "op": "create_physical",221 "op": "create_physical",
177 "mac_address": mac,222 "mac_address": mac,
@@ -203,7 +248,7 @@
203 mac = factory.make_mac_address()248 mac = factory.make_mac_address()
204 name = factory.make_name("eth")249 name = factory.make_name("eth")
205 vlan = factory.make_VLAN()250 vlan = factory.make_VLAN()
206 uri = get_node_interfaces_uri(node)251 uri = get_interfaces_uri(node)
207 response = self.client.post(uri, {252 response = self.client.post(uri, {
208 "op": "create_physical",253 "op": "create_physical",
209 "mac_address": mac,254 "mac_address": mac,
@@ -216,7 +261,7 @@
216 def test_create_physical_requires_mac_name_and_vlan(self):261 def test_create_physical_requires_mac_name_and_vlan(self):
217 self.become_admin()262 self.become_admin()
218 node = factory.make_Node(status=NODE_STATUS.READY)263 node = factory.make_Node(status=NODE_STATUS.READY)
219 uri = get_node_interfaces_uri(node)264 uri = get_interfaces_uri(node)
220 response = self.client.post(uri, {265 response = self.client.post(uri, {
221 "op": "create_physical",266 "op": "create_physical",
222 })267 })
@@ -235,7 +280,7 @@
235 INTERFACE_TYPE.PHYSICAL)280 INTERFACE_TYPE.PHYSICAL)
236 name = factory.make_name("eth")281 name = factory.make_name("eth")
237 vlan = factory.make_VLAN()282 vlan = factory.make_VLAN()
238 uri = get_node_interfaces_uri(node)283 uri = get_interfaces_uri(node)
239 response = self.client.post(uri, {284 response = self.client.post(uri, {
240 "op": "create_physical",285 "op": "create_physical",
241 "mac_address": "%s" % interface_on_other_node.mac_address,286 "mac_address": "%s" % interface_on_other_node.mac_address,
@@ -264,7 +309,7 @@
264 factory.make_name("tag")309 factory.make_name("tag")
265 for _ in range(3)310 for _ in range(3)
266 ]311 ]
267 uri = get_node_interfaces_uri(node)312 uri = get_interfaces_uri(node)
268 response = self.client.post(uri, {313 response = self.client.post(uri, {
269 "op": "create_bond",314 "op": "create_bond",
270 "mac_address": "%s" % parent_1_iface.mac_address,315 "mac_address": "%s" % parent_1_iface.mac_address,
@@ -291,6 +336,17 @@
291 parent_2_iface.name,336 parent_2_iface.name,
292 ], parsed_interface['parents'])337 ], parsed_interface['parents'])
293338
339 def test_create_bond_404_on_device(self):
340 parent = factory.make_Node()
341 device = factory.make_Node(
342 owner=self.logged_in_user, installable=False, parent=parent)
343 uri = get_interfaces_uri(device)
344 response = self.client.post(uri, {
345 "op": "create_bond",
346 })
347 self.assertEqual(
348 httplib.NOT_FOUND, response.status_code, response.content)
349
294 def test_create_bond_requires_admin(self):350 def test_create_bond_requires_admin(self):
295 node = factory.make_Node()351 node = factory.make_Node()
296 vlan = factory.make_VLAN()352 vlan = factory.make_VLAN()
@@ -299,7 +355,7 @@
299 parent_2_iface = factory.make_Interface(355 parent_2_iface = factory.make_Interface(
300 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node)356 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node)
301 name = factory.make_name("bond")357 name = factory.make_name("bond")
302 uri = get_node_interfaces_uri(node)358 uri = get_interfaces_uri(node)
303 response = self.client.post(uri, {359 response = self.client.post(uri, {
304 "op": "create_bond",360 "op": "create_bond",
305 "mac": "%s" % parent_1_iface.mac_address,361 "mac": "%s" % parent_1_iface.mac_address,
@@ -335,7 +391,7 @@
335 parent_2_iface = factory.make_Interface(391 parent_2_iface = factory.make_Interface(
336 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node)392 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node)
337 name = factory.make_name("bond")393 name = factory.make_name("bond")
338 uri = get_node_interfaces_uri(node)394 uri = get_interfaces_uri(node)
339 response = self.client.post(uri, {395 response = self.client.post(uri, {
340 "op": "create_bond",396 "op": "create_bond",
341 "mac": "%s" % parent_1_iface.mac_address,397 "mac": "%s" % parent_1_iface.mac_address,
@@ -349,7 +405,7 @@
349 def test_create_bond_requires_name_vlan_and_parents(self):405 def test_create_bond_requires_name_vlan_and_parents(self):
350 self.become_admin()406 self.become_admin()
351 node = factory.make_Node(status=NODE_STATUS.READY)407 node = factory.make_Node(status=NODE_STATUS.READY)
352 uri = get_node_interfaces_uri(node)408 uri = get_interfaces_uri(node)
353 response = self.client.post(uri, {409 response = self.client.post(uri, {
354 "op": "create_bond",410 "op": "create_bond",
355 })411 })
@@ -374,7 +430,7 @@
374 factory.make_name("tag")430 factory.make_name("tag")
375 for _ in range(3)431 for _ in range(3)
376 ]432 ]
377 uri = get_node_interfaces_uri(node)433 uri = get_interfaces_uri(node)
378 response = self.client.post(uri, {434 response = self.client.post(uri, {
379 "op": "create_vlan",435 "op": "create_vlan",
380 "vlan": tagged_vlan.id,436 "vlan": tagged_vlan.id,
@@ -394,13 +450,24 @@
394 "tags": Equals(tags),450 "tags": Equals(tags),
395 }))451 }))
396452
453 def test_create_vlan_404_on_device(self):
454 parent = factory.make_Node()
455 device = factory.make_Node(
456 owner=self.logged_in_user, installable=False, parent=parent)
457 uri = get_interfaces_uri(device)
458 response = self.client.post(uri, {
459 "op": "create_vlan",
460 })
461 self.assertEqual(
462 httplib.NOT_FOUND, response.status_code, response.content)
463
397 def test_create_vlan_requires_admin(self):464 def test_create_vlan_requires_admin(self):
398 node = factory.make_Node()465 node = factory.make_Node()
399 untagged_vlan = factory.make_VLAN()466 untagged_vlan = factory.make_VLAN()
400 parent_iface = factory.make_Interface(467 parent_iface = factory.make_Interface(
401 INTERFACE_TYPE.PHYSICAL, vlan=untagged_vlan, node=node)468 INTERFACE_TYPE.PHYSICAL, vlan=untagged_vlan, node=node)
402 tagged_vlan = factory.make_VLAN()469 tagged_vlan = factory.make_VLAN()
403 uri = get_node_interfaces_uri(node)470 uri = get_interfaces_uri(node)
404 response = self.client.post(uri, {471 response = self.client.post(uri, {
405 "op": "create_vlan",472 "op": "create_vlan",
406 "vlan": tagged_vlan.vid,473 "vlan": tagged_vlan.vid,
@@ -412,7 +479,7 @@
412 def test_create_vlan_requires_vlan_and_parent(self):479 def test_create_vlan_requires_vlan_and_parent(self):
413 self.become_admin()480 self.become_admin()
414 node = factory.make_Node()481 node = factory.make_Node()
415 uri = get_node_interfaces_uri(node)482 uri = get_interfaces_uri(node)
416 response = self.client.post(uri, {483 response = self.client.post(uri, {
417 "op": "create_vlan",484 "op": "create_vlan",
418 })485 })
@@ -433,7 +500,7 @@
433 self.assertEqual(500 self.assertEqual(
434 '/api/1.0/nodes/%s/interfaces/%s/' % (501 '/api/1.0/nodes/%s/interfaces/%s/' % (
435 node.system_id, interface.id),502 node.system_id, interface.id),
436 get_node_interface_uri(interface, node=node))503 get_interface_uri(interface, node=node))
437504
438 def test_read(self):505 def test_read(self):
439 node = factory.make_Node()506 node = factory.make_Node()
@@ -500,7 +567,7 @@
500 }567 }
501 bond.save()568 bond.save()
502569
503 uri = get_node_interface_uri(bond)570 uri = get_interface_uri(bond)
504 response = self.client.get(uri)571 response = self.client.get(uri)
505 self.assertEqual(httplib.OK, response.status_code, response.content)572 self.assertEqual(httplib.OK, response.status_code, response.content)
506 parsed_interface = json.loads(response.content)573 parsed_interface = json.loads(response.content)
@@ -513,7 +580,7 @@
513 }),580 }),
514 "mac_address": Equals("%s" % bond.mac_address),581 "mac_address": Equals("%s" % bond.mac_address),
515 "tags": Equals(bond.tags),582 "tags": Equals(bond.tags),
516 "resource_uri": Equals(get_node_interface_uri(bond)),583 "resource_uri": Equals(get_interface_uri(bond)),
517 "params": Equals(bond.params),584 "params": Equals(bond.params),
518 "effective_mtu": Equals(bond.get_effective_mtu()),585 "effective_mtu": Equals(bond.get_effective_mtu()),
519 }))586 }))
@@ -533,17 +600,28 @@
533 def test_read_by_specifier(self):600 def test_read_by_specifier(self):
534 node = factory.make_Node(hostname="tasty-biscuits")601 node = factory.make_Node(hostname="tasty-biscuits")
535 bond0, _, _ = make_complex_interface(node, name="bond0")602 bond0, _, _ = make_complex_interface(node, name="bond0")
536 uri = get_node_interface_uri(603 uri = get_interface_uri(
537 "hostname:tasty-biscuits,name:bond0", node=node)604 "hostname:tasty-biscuits,name:bond0", node=node)
538 response = self.client.get(uri)605 response = self.client.get(uri)
539 self.assertEqual(httplib.OK, response.status_code, response.content)606 self.assertEqual(httplib.OK, response.status_code, response.content)
540 parsed_interface = json.loads(response.content)607 parsed_interface = json.loads(response.content)
541 self.assertEqual(bond0.id, parsed_interface['id'])608 self.assertEqual(bond0.id, parsed_interface['id'])
542609
610 def test_read_device_interface(self):
611 parent = factory.make_Node()
612 device = factory.make_Node(installable=False, parent=parent)
613 interface = factory.make_Interface(
614 INTERFACE_TYPE.PHYSICAL, node=device)
615 uri = get_interface_uri(interface)
616 response = self.client.get(uri)
617 self.assertEqual(httplib.OK, response.status_code, response.content)
618 parsed_interface = json.loads(response.content)
619 self.assertEqual(interface.id, parsed_interface['id'])
620
543 def test_read_404_when_invalid_id(self):621 def test_read_404_when_invalid_id(self):
544 node = factory.make_Node()622 node = factory.make_Node()
545 uri = reverse(623 uri = reverse(
546 'node_interface_handler',624 'interface_handler',
547 args=[node.system_id, random.randint(100, 1000)])625 args=[node.system_id, random.randint(100, 1000)])
548 response = self.client.get(uri)626 response = self.client.get(uri)
549 self.assertEqual(627 self.assertEqual(
@@ -557,7 +635,7 @@
557 INTERFACE_TYPE.PHYSICAL, node=node)635 INTERFACE_TYPE.PHYSICAL, node=node)
558 new_name = factory.make_name("name")636 new_name = factory.make_name("name")
559 new_vlan = factory.make_VLAN()637 new_vlan = factory.make_VLAN()
560 uri = get_node_interface_uri(interface)638 uri = get_interface_uri(interface)
561 response = self.client.put(uri, {639 response = self.client.put(uri, {
562 "name": new_name,640 "name": new_name,
563 "vlan": new_vlan.id,641 "vlan": new_vlan.id,
@@ -568,13 +646,32 @@
568 self.assertEquals(new_name, parsed_interface["name"])646 self.assertEquals(new_name, parsed_interface["name"])
569 self.assertEquals(new_vlan.vid, parsed_interface["vlan"]["vid"])647 self.assertEquals(new_vlan.vid, parsed_interface["vlan"]["vid"])
570648
649 def test_update_device_physical_interface(self):
650 node = factory.make_Node()
651 device = factory.make_Node(
652 owner=self.logged_in_user, installable=False, parent=node)
653 interface = factory.make_Interface(
654 INTERFACE_TYPE.PHYSICAL, node=device)
655 new_name = factory.make_name("name")
656 new_vlan = factory.make_VLAN()
657 uri = get_interface_uri(interface)
658 response = self.client.put(uri, {
659 "name": new_name,
660 "vlan": new_vlan.id,
661 })
662 self.assertEqual(
663 httplib.OK, response.status_code, response.content)
664 parsed_interface = json.loads(response.content)
665 self.assertEquals(new_name, parsed_interface["name"])
666 self.assertEquals(new_vlan.vid, parsed_interface["vlan"]["vid"])
667
571 def test_update_bond_interface(self):668 def test_update_bond_interface(self):
572 self.become_admin()669 self.become_admin()
573 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):670 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
574 node = factory.make_Node(status=status)671 node = factory.make_Node(status=status)
575 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(672 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(
576 node)673 node)
577 uri = get_node_interface_uri(bond)674 uri = get_interface_uri(bond)
578 response = self.client.put(uri, {675 response = self.client.put(uri, {
579 "parents": [nic_0.id],676 "parents": [nic_0.id],
580 })677 })
@@ -591,7 +688,7 @@
591 node)688 node)
592 physical_interface = factory.make_Interface(689 physical_interface = factory.make_Interface(
593 INTERFACE_TYPE.PHYSICAL, node=node)690 INTERFACE_TYPE.PHYSICAL, node=node)
594 uri = get_node_interface_uri(vlan_10)691 uri = get_interface_uri(vlan_10)
595 response = self.client.put(uri, {692 response = self.client.put(uri, {
596 "parent": physical_interface.id,693 "parent": physical_interface.id,
597 })694 })
@@ -605,7 +702,7 @@
605 node = factory.make_Node()702 node = factory.make_Node()
606 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)703 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
607 new_name = factory.make_name("name")704 new_name = factory.make_name("name")
608 uri = get_node_interface_uri(interface)705 uri = get_interface_uri(interface)
609 response = self.client.put(uri, {706 response = self.client.put(uri, {
610 "name": new_name,707 "name": new_name,
611 })708 })
@@ -634,7 +731,7 @@
634 interface = factory.make_Interface(731 interface = factory.make_Interface(
635 INTERFACE_TYPE.PHYSICAL, node=node)732 INTERFACE_TYPE.PHYSICAL, node=node)
636 new_name = factory.make_name("name")733 new_name = factory.make_name("name")
637 uri = get_node_interface_uri(interface)734 uri = get_interface_uri(interface)
638 response = self.client.put(uri, {735 response = self.client.put(uri, {
639 "name": new_name,736 "name": new_name,
640 })737 })
@@ -646,25 +743,38 @@
646 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):743 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
647 node = factory.make_Node(interface=True, status=status)744 node = factory.make_Node(interface=True, status=status)
648 interface = node.get_boot_interface()745 interface = node.get_boot_interface()
649 uri = get_node_interface_uri(interface)746 uri = get_interface_uri(interface)
650 response = self.client.delete(uri)747 response = self.client.delete(uri)
651 self.assertEqual(748 self.assertEqual(
652 httplib.NO_CONTENT, response.status_code, response.content)749 httplib.NO_CONTENT, response.status_code, response.content)
653 self.assertIsNone(reload_object(interface))750 self.assertIsNone(reload_object(interface))
654751
752 def test_delete_deletes_device_interface(self):
753 parent = factory.make_Node()
754 device = factory.make_Node(
755 owner=self.logged_in_user, installable=False, parent=parent)
756 interface = factory.make_Interface(
757 INTERFACE_TYPE.PHYSICAL, node=device)
758 uri = get_interface_uri(interface)
759 response = self.client.delete(uri)
760 self.assertEqual(
761 httplib.NO_CONTENT, response.status_code, response.content)
762 self.assertIsNone(reload_object(interface))
763
655 def test_delete_403_when_not_admin(self):764 def test_delete_403_when_not_admin(self):
656 node = factory.make_Node(interface=True)765 node = factory.make_Node(interface=True)
657 interface = node.get_boot_interface()766 interface = node.get_boot_interface()
658 uri = get_node_interface_uri(interface)767 uri = get_interface_uri(interface)
659 response = self.client.delete(uri)768 response = self.client.delete(uri)
660 self.assertEqual(769 self.assertEqual(
661 httplib.FORBIDDEN, response.status_code, response.content)770 httplib.FORBIDDEN, response.status_code, response.content)
662 self.assertIsNotNone(reload_object(interface))771 self.assertIsNotNone(reload_object(interface))
663772
664 def test_delete_404_when_invalid_id(self):773 def test_delete_404_when_invalid_id(self):
774 self.become_admin()
665 node = factory.make_Node()775 node = factory.make_Node()
666 uri = reverse(776 uri = reverse(
667 'node_interface_handler',777 'interface_handler',
668 args=[node.system_id, random.randint(100, 1000)])778 args=[node.system_id, random.randint(100, 1000)])
669 response = self.client.delete(uri)779 response = self.client.delete(uri)
670 self.assertEqual(780 self.assertEqual(
@@ -690,7 +800,7 @@
690 ):800 ):
691 node = factory.make_Node(interface=True, status=status)801 node = factory.make_Node(interface=True, status=status)
692 interface = node.get_boot_interface()802 interface = node.get_boot_interface()
693 uri = get_node_interface_uri(interface)803 uri = get_interface_uri(interface)
694 response = self.client.delete(uri)804 response = self.client.delete(uri)
695 self.assertEqual(805 self.assertEqual(
696 httplib.CONFLICT, response.status_code, response.content)806 httplib.CONFLICT, response.status_code, response.content)
@@ -703,7 +813,7 @@
703 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):813 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
704 node = factory.make_Node(interface=True, status=status)814 node = factory.make_Node(interface=True, status=status)
705 interface = node.get_boot_interface()815 interface = node.get_boot_interface()
706 uri = get_node_interface_uri(interface)816 uri = get_interface_uri(interface)
707 response = self.client.post(uri, {817 response = self.client.post(uri, {
708 "op": "link_subnet",818 "op": "link_subnet",
709 "mode": INTERFACE_LINK_TYPE.DHCP,819 "mode": INTERFACE_LINK_TYPE.DHCP,
@@ -716,12 +826,51 @@
716 "mode": Equals(INTERFACE_LINK_TYPE.DHCP),826 "mode": Equals(INTERFACE_LINK_TYPE.DHCP),
717 }))827 }))
718828
829 def test_link_subnet_creates_link_on_device(self):
830 parent = factory.make_Node()
831 device = factory.make_Node(
832 owner=self.logged_in_user, installable=False, parent=parent)
833 interface = factory.make_Interface(
834 INTERFACE_TYPE.PHYSICAL, node=device)
835 subnet = factory.make_Subnet(vlan=interface.vlan)
836 uri = get_interface_uri(interface)
837 response = self.client.post(uri, {
838 "op": "link_subnet",
839 "mode": INTERFACE_LINK_TYPE.STATIC,
840 "subnet": subnet.id,
841 })
842 self.assertEqual(
843 httplib.OK, response.status_code, response.content)
844 parsed_response = json.loads(response.content)
845 self.assertThat(
846 parsed_response["links"][0], ContainsDict({
847 "mode": Equals(INTERFACE_LINK_TYPE.STATIC),
848 }))
849
850 def test_link_subnet_on_device_only_allows_static(self):
851 parent = factory.make_Node()
852 device = factory.make_Node(
853 owner=self.logged_in_user, installable=False, parent=parent)
854 interface = factory.make_Interface(
855 INTERFACE_TYPE.PHYSICAL, node=device)
856 for link_type in [
857 INTERFACE_LINK_TYPE.AUTO,
858 INTERFACE_LINK_TYPE.DHCP,
859 INTERFACE_LINK_TYPE.LINK_UP]:
860 uri = get_interface_uri(interface)
861 response = self.client.post(uri, {
862 "op": "link_subnet",
863 "mode": link_type,
864 })
865 self.assertEqual(
866 httplib.BAD_REQUEST, response.status_code, response.content)
867
719 def test_link_subnet_raises_error(self):868 def test_link_subnet_raises_error(self):
720 self.become_admin()869 self.become_admin()
721 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):870 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
722 node = factory.make_Node(interface=True, status=status)871 node = factory.make_Node(interface=True, status=status)
723 interface = node.get_boot_interface()872 interface = node.get_boot_interface()
724 uri = get_node_interface_uri(interface)873 uri = get_interface_uri(interface)
725 response = self.client.post(uri, {874 response = self.client.post(uri, {
726 "op": "link_subnet",875 "op": "link_subnet",
727 })876 })
@@ -734,7 +883,7 @@
734 def test_link_subnet_requries_admin(self):883 def test_link_subnet_requries_admin(self):
735 node = factory.make_Node(interface=True)884 node = factory.make_Node(interface=True)
736 interface = node.get_boot_interface()885 interface = node.get_boot_interface()
737 uri = get_node_interface_uri(interface)886 uri = get_interface_uri(interface)
738 response = self.client.post(uri, {887 response = self.client.post(uri, {
739 "op": "link_subnet",888 "op": "link_subnet",
740 })889 })
@@ -761,7 +910,7 @@
761 ):910 ):
762 node = factory.make_Node(interface=True, status=status)911 node = factory.make_Node(interface=True, status=status)
763 interface = node.get_boot_interface()912 interface = node.get_boot_interface()
764 uri = get_node_interface_uri(interface)913 uri = get_interface_uri(interface)
765 response = self.client.post(uri, {914 response = self.client.post(uri, {
766 "op": "link_subnet",915 "op": "link_subnet",
767 "mode": INTERFACE_LINK_TYPE.DHCP,916 "mode": INTERFACE_LINK_TYPE.DHCP,
@@ -781,7 +930,7 @@
781 dhcp_ip = factory.make_StaticIPAddress(930 dhcp_ip = factory.make_StaticIPAddress(
782 alloc_type=IPADDRESS_TYPE.DHCP, ip="",931 alloc_type=IPADDRESS_TYPE.DHCP, ip="",
783 subnet=subnet, interface=interface)932 subnet=subnet, interface=interface)
784 uri = get_node_interface_uri(interface)933 uri = get_interface_uri(interface)
785 response = self.client.post(uri, {934 response = self.client.post(uri, {
786 "op": "unlink_subnet",935 "op": "unlink_subnet",
787 "id": dhcp_ip.id,936 "id": dhcp_ip.id,
@@ -790,12 +939,31 @@
790 httplib.OK, response.status_code, response.content)939 httplib.OK, response.status_code, response.content)
791 self.assertIsNone(reload_object(dhcp_ip))940 self.assertIsNone(reload_object(dhcp_ip))
792941
942 def test_unlink_subnet_deletes_link_on_device(self):
943 parent = factory.make_Node()
944 device = factory.make_Node(
945 owner=self.logged_in_user, installable=False, parent=parent)
946 interface = factory.make_Interface(
947 INTERFACE_TYPE.PHYSICAL, node=device)
948 subnet = factory.make_Subnet()
949 static_ip = factory.make_StaticIPAddress(
950 alloc_type=IPADDRESS_TYPE.STICKY,
951 subnet=subnet, interface=interface)
952 uri = get_interface_uri(interface)
953 response = self.client.post(uri, {
954 "op": "unlink_subnet",
955 "id": static_ip.id,
956 })
957 self.assertEqual(
958 httplib.OK, response.status_code, response.content)
959 self.assertIsNone(reload_object(static_ip))
960
793 def test_unlink_subnet_raises_error(self):961 def test_unlink_subnet_raises_error(self):
794 self.become_admin()962 self.become_admin()
795 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):963 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
796 node = factory.make_Node(interface=True, status=status)964 node = factory.make_Node(interface=True, status=status)
797 interface = node.get_boot_interface()965 interface = node.get_boot_interface()
798 uri = get_node_interface_uri(interface)966 uri = get_interface_uri(interface)
799 response = self.client.post(uri, {967 response = self.client.post(uri, {
800 "op": "unlink_subnet",968 "op": "unlink_subnet",
801 })969 })
@@ -808,7 +976,7 @@
808 def test_unlink_subnet_requries_admin(self):976 def test_unlink_subnet_requries_admin(self):
809 node = factory.make_Node(interface=True)977 node = factory.make_Node(interface=True)
810 interface = node.get_boot_interface()978 interface = node.get_boot_interface()
811 uri = get_node_interface_uri(interface)979 uri = get_interface_uri(interface)
812 response = self.client.post(uri, {980 response = self.client.post(uri, {
813 "op": "unlink_subnet",981 "op": "unlink_subnet",
814 })982 })
@@ -835,7 +1003,7 @@
835 ):1003 ):
836 node = factory.make_Node(interface=True, status=status)1004 node = factory.make_Node(interface=True, status=status)
837 interface = node.get_boot_interface()1005 interface = node.get_boot_interface()
838 uri = get_node_interface_uri(interface)1006 uri = get_interface_uri(interface)
839 response = self.client.post(uri, {1007 response = self.client.post(uri, {
840 "op": "unlink_subnet",1008 "op": "unlink_subnet",
841 })1009 })
@@ -855,7 +1023,7 @@
855 link_ip = factory.make_StaticIPAddress(1023 link_ip = factory.make_StaticIPAddress(
856 alloc_type=IPADDRESS_TYPE.AUTO, ip="",1024 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
857 subnet=subnet, interface=interface)1025 subnet=subnet, interface=interface)
858 uri = get_node_interface_uri(interface)1026 uri = get_interface_uri(interface)
859 response = self.client.post(uri, {1027 response = self.client.post(uri, {
860 "op": "set_default_gateway",1028 "op": "set_default_gateway",
861 "link_id": link_ip.id1029 "link_id": link_ip.id
@@ -877,7 +1045,7 @@
877 link_ip = factory.make_StaticIPAddress(1045 link_ip = factory.make_StaticIPAddress(
878 alloc_type=IPADDRESS_TYPE.AUTO, ip="",1046 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
879 subnet=subnet, interface=interface)1047 subnet=subnet, interface=interface)
880 uri = get_node_interface_uri(interface)1048 uri = get_interface_uri(interface)
881 response = self.client.post(uri, {1049 response = self.client.post(uri, {
882 "op": "set_default_gateway",1050 "op": "set_default_gateway",
883 "link_id": link_ip.id1051 "link_id": link_ip.id
@@ -891,7 +1059,7 @@
891 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1059 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):
892 node = factory.make_Node(interface=True, status=status)1060 node = factory.make_Node(interface=True, status=status)
893 interface = node.get_boot_interface()1061 interface = node.get_boot_interface()
894 uri = get_node_interface_uri(interface)1062 uri = get_interface_uri(interface)
895 response = self.client.post(uri, {1063 response = self.client.post(uri, {
896 "op": "set_default_gateway",1064 "op": "set_default_gateway",
897 })1065 })
@@ -904,7 +1072,7 @@
904 def test_set_default_gateway_requries_admin(self):1072 def test_set_default_gateway_requries_admin(self):
905 node = factory.make_Node(interface=True)1073 node = factory.make_Node(interface=True)
906 interface = node.get_boot_interface()1074 interface = node.get_boot_interface()
907 uri = get_node_interface_uri(interface)1075 uri = get_interface_uri(interface)
908 response = self.client.post(uri, {1076 response = self.client.post(uri, {
909 "op": "set_default_gateway",1077 "op": "set_default_gateway",
910 })1078 })
@@ -931,7 +1099,7 @@
931 ):1099 ):
932 node = factory.make_Node(interface=True, status=status)1100 node = factory.make_Node(interface=True, status=status)
933 interface = node.get_boot_interface()1101 interface = node.get_boot_interface()
934 uri = get_node_interface_uri(interface)1102 uri = get_interface_uri(interface)
935 response = self.client.post(uri, {1103 response = self.client.post(uri, {
936 "op": "set_default_gateway",1104 "op": "set_default_gateway",
937 })1105 })
9381106
=== modified file 'src/maasserver/forms_interface_link.py'
--- src/maasserver/forms_interface_link.py 2015-11-05 23:56:42 +0000
+++ src/maasserver/forms_interface_link.py 2015-12-07 14:29:30 +0000
@@ -52,13 +52,6 @@
52class InterfaceLinkForm(forms.Form):52class InterfaceLinkForm(forms.Form):
53 """Interface link form."""53 """Interface link form."""
5454
55 mode = CaseInsensitiveChoiceField(
56 choices=INTERFACE_LINK_TYPE_CHOICES, required=True,
57 error_messages={
58 'invalid_choice': compose_invalid_choice_text(
59 'mode', INTERFACE_LINK_TYPE_CHOICES),
60 })
61
62 subnet = SpecifierOrModelChoiceField(queryset=None, required=False)55 subnet = SpecifierOrModelChoiceField(queryset=None, required=False)
6356
64 ip_address = forms.GenericIPAddressField(required=False)57 ip_address = forms.GenericIPAddressField(required=False)
@@ -66,8 +59,29 @@
66 default_gateway = forms.BooleanField(initial=False, required=False)59 default_gateway = forms.BooleanField(initial=False, required=False)
6760
68 def __init__(self, *args, **kwargs):61 def __init__(self, *args, **kwargs):
62 # Get list of allowed modes for this interface.
63 allowed_modes = kwargs.pop("allowed_modes", [
64 INTERFACE_LINK_TYPE.AUTO,
65 INTERFACE_LINK_TYPE.DHCP,
66 INTERFACE_LINK_TYPE.STATIC,
67 INTERFACE_LINK_TYPE.LINK_UP,
68 ])
69 mode_choices = [
70 (key, value)
71 for key, value in INTERFACE_LINK_TYPE_CHOICES
72 if key in allowed_modes
73 ]
74
69 self.instance = kwargs.pop("instance")75 self.instance = kwargs.pop("instance")
70 super(InterfaceLinkForm, self).__init__(*args, **kwargs)76 super(InterfaceLinkForm, self).__init__(*args, **kwargs)
77
78 # Create the mode field and setup the queryset on the subnet.
79 self.fields['mode'] = CaseInsensitiveChoiceField(
80 choices=mode_choices, required=True,
81 error_messages={
82 'invalid_choice': compose_invalid_choice_text(
83 'mode', mode_choices),
84 })
71 self.fields['subnet'].queryset = self.instance.vlan.subnet_set.all()85 self.fields['subnet'].queryset = self.instance.vlan.subnet_set.all()
7286
73 def clean(self):87 def clean(self):
7488
=== modified file 'src/maasserver/models/__init__.py'
--- src/maasserver/models/__init__.py 2015-09-17 23:00:17 +0000
+++ src/maasserver/models/__init__.py 2015-12-07 14:29:30 +0000
@@ -263,7 +263,26 @@
263 raise NotImplementedError(263 raise NotImplementedError(
264 'Invalid permission check (invalid permission name: %s).' %264 'Invalid permission check (invalid permission name: %s).' %
265 perm)265 perm)
266 elif isinstance(obj, (Fabric, FanNetwork, Interface, Subnet, Space)):266 elif isinstance(obj, Interface):
267 if perm == NODE_PERMISSION.VIEW:
268 # Any registered user can view a interface regardless
269 # of its state.
270 return True
271 elif perm in NODE_PERMISSION.EDIT:
272 # A device can be editted by its owner a node must be admin.
273 node = obj.get_node()
274 if node is None or node.installable:
275 return user.is_superuser
276 else:
277 return node.owner == user
278 elif perm in NODE_PERMISSION.ADMIN:
279 # Admin permission is solely granted to superusers.
280 return user.is_superuser
281 else:
282 raise NotImplementedError(
283 'Invalid permission check (invalid permission name: %s).' %
284 perm)
285 elif isinstance(obj, (Fabric, FanNetwork, Subnet, Space)):
267 if perm == NODE_PERMISSION.VIEW:286 if perm == NODE_PERMISSION.VIEW:
268 # Any registered user can view a fabric or interface regardless287 # Any registered user can view a fabric or interface regardless
269 # of its state.288 # of its state.
270289
=== modified file 'src/maasserver/tests/test_auth.py'
--- src/maasserver/tests/test_auth.py 2015-08-31 21:24:55 +0000
+++ src/maasserver/tests/test_auth.py 2015-12-07 14:29:30 +0000
@@ -232,6 +232,34 @@
232 user, NODE_PERMISSION.ADMIN, factory.make_FilesystemGroup()))232 user, NODE_PERMISSION.ADMIN, factory.make_FilesystemGroup()))
233233
234234
235class TestMAASAuthorizationBackendForDeviceInterface(MAASServerTestCase):
236
237 def test_owner_can_edit_device_interface(self):
238 backend = MAASAuthorizationBackend()
239 user = factory.make_User()
240 parent = factory.make_Node()
241 device = factory.make_Node(
242 owner=user, installable=False, parent=parent)
243 interface = factory.make_Interface(
244 INTERFACE_TYPE.PHYSICAL, node=device)
245 self.assertTrue(
246 backend.has_perm(
247 user, NODE_PERMISSION.EDIT, interface))
248
249 def test_non_owner_cannot_edit_device_interface(self):
250 backend = MAASAuthorizationBackend()
251 user = factory.make_User()
252 owner = factory.make_User()
253 parent = factory.make_Node()
254 device = factory.make_Node(
255 owner=owner, installable=False, parent=parent)
256 interface = factory.make_Interface(
257 INTERFACE_TYPE.PHYSICAL, node=device)
258 self.assertFalse(
259 backend.has_perm(
260 user, NODE_PERMISSION.EDIT, interface))
261
262
235class TestMAASAuthorizationBackendForNetworking(MAASServerTestCase):263class TestMAASAuthorizationBackendForNetworking(MAASServerTestCase):
236264
237 scenarios = (265 scenarios = (
238266
=== modified file 'src/maasserver/urls_api.py'
--- src/maasserver/urls_api.py 2015-11-10 00:00:54 +0000
+++ src/maasserver/urls_api.py 2015-12-07 14:29:30 +0000
@@ -76,6 +76,8 @@
76 FilesHandler,76 FilesHandler,
77)77)
78from maasserver.api.interfaces import (78from maasserver.api.interfaces import (
79 InterfaceHandler,
80 InterfacesHandler,
79 NodeInterfaceHandler,81 NodeInterfaceHandler,
80 NodeInterfacesHandler,82 NodeInterfacesHandler,
81)83)
@@ -197,6 +199,10 @@
197 BcacheCacheSetHandler, authentication=api_auth)199 BcacheCacheSetHandler, authentication=api_auth)
198bcache_cache_sets_handler = RestrictedResource(200bcache_cache_sets_handler = RestrictedResource(
199 BcacheCacheSetsHandler, authentication=api_auth)201 BcacheCacheSetsHandler, authentication=api_auth)
202interface_handler = RestrictedResource(
203 InterfaceHandler, authentication=api_auth)
204interfaces_handler = RestrictedResource(
205 InterfacesHandler, authentication=api_auth)
200node_interface_handler = RestrictedResource(206node_interface_handler = RestrictedResource(
201 NodeInterfaceHandler, authentication=api_auth)207 NodeInterfaceHandler, authentication=api_auth)
202node_interfaces_handler = RestrictedResource(208node_interfaces_handler = RestrictedResource(
@@ -308,8 +314,14 @@
308 '(?P<cache_set_id>[^/]+)/$',314 '(?P<cache_set_id>[^/]+)/$',
309 bcache_cache_set_handler, name='bcache_cache_set_handler'),315 bcache_cache_set_handler, name='bcache_cache_set_handler'),
310 url(r'^nodes/(?P<system_id>[^/]+)/interfaces/(?P<interface_id>[^/]+)/$',316 url(r'^nodes/(?P<system_id>[^/]+)/interfaces/(?P<interface_id>[^/]+)/$',
317 interface_handler, name='interface_handler'),
318 url(
319 r'^nodes/(?P<system_id>[^/]+)/node-interfaces/'
320 '(?P<interface_id>[^/]+)/$',
311 node_interface_handler, name='node_interface_handler'),321 node_interface_handler, name='node_interface_handler'),
312 url(r'^nodes/(?P<system_id>[^/]+)/interfaces/$',322 url(r'^nodes/(?P<system_id>[^/]+)/interfaces/$',
323 interfaces_handler, name='interfaces_handler'),
324 url(r'^nodes/(?P<system_id>[^/]+)/node-interfaces/$',
313 node_interfaces_handler, name='node_interfaces_handler'),325 node_interfaces_handler, name='node_interfaces_handler'),
314 url(326 url(
315 r'^nodes/(?P<system_id>[^/]+)/$', node_handler,327 r'^nodes/(?P<system_id>[^/]+)/$', node_handler,

Subscribers

People subscribed via source and target branches