Merge lp:~jtv/maas/api-list-networks into lp:~maas-committers/maas/trunk

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: no longer in the source branch.
Merged at revision: 1917
Proposed branch: lp:~jtv/maas/api-list-networks
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 157 lines (+89/-6)
3 files modified
src/maasserver/api.py (+15/-5)
src/maasserver/tests/test_api_networks.py (+70/-0)
src/maasserver/urls_api.py (+4/-1)
To merge this branch: bzr merge lp:~jtv/maas/api-list-networks
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+205525@code.launchpad.net

Commit message

"List networks" API call.

Description of the change

In doing this I noticed a change in our API style: where we used to write "list" operations for the "plural" resources, unless we need to parameterise the listing, we now just implement GET on the resource. So to list all nodes, you call the nodes handler's list() method, but to list all zones, you just GET ".../zones/". I followed the new style here, but I'm not sure the CLI can deal with this. Maybe we ought to have both for all resources.

Jeroen

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

[0]

50 === added file 'src/maasserver/tests/test_api_networks.py'
[...]
83 + def test_list_returns_network(self):

The name of this test is strange. One would expect test_list_returns_network*s*

[1]

50 === added file 'src/maasserver/tests/test_api_networks.py'
[...]
83 + def test_list_returns_network(self):

I put a break point in this test and look:

(Pdb) parsed_result
[{u'description': u'tjBXqFQ7vK', u'ip': u'217.187.192.0', u'_state': u'<django.db.models.base.ModelState object at 0x962acd0>', u'netmask': u'255.255.192.0', u'vlan_tag': None, u'id': 1, u'name': u'6THuCI'}]

Django adds clutter to the objects (I'm talking about the '_state' field) so we need to explicitly list the fields that we want to return.

[2]

50 === added file 'src/maasserver/tests/test_api_networks.py'
[...]
93 + (
94 + original_network.name,
95 + original_network.ip,
96 + original_network.netmask,
97 + original_network.vlan_tag,
98 + original_network.description,
99 + ),
100 + (
101 + returned_network['name'],
102 + returned_network['ip'],
103 + returned_network['netmask'],
104 + returned_network['vlan_tag'],
105 + returned_network['description'],
106 + ))

Couldn't this benefit from self.assertAttributes?

[3]

50 === added file 'src/maasserver/tests/test_api_networks.py'
[...]
93 + (
94 + original_network.name,
95 + original_network.ip,
96 + original_network.netmask,
97 + original_network.vlan_tag,
98 + original_network.description,
99 + ),
100 + (
101 + returned_network['name'],
102 + returned_network['ip'],
103 + returned_network['netmask'],
104 + returned_network['vlan_tag'],
105 + returned_network['description'],
106 + ))

There is a bug: we should also have a 'resource_uri' field.

Revision history for this message
Raphaël Badin (rvb) wrote :

No further remarks :).

[1] and [3] are, I think, blocker but feel free to fix that in follow-up branches.

I see you've added the 'resource_uri' field in https://code.launchpad.net/~jtv/maas/api-add-edit-network/+merge/205539 so it's just a matter of adding a test to fix [3].

review: Approve
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

> [0]
>
> 50 === added file 'src/maasserver/tests/test_api_networks.py'
> [...]
> 83 + def test_list_returns_network(self):
>
> The name of this test is strange. One would expect
> test_list_returns_network*s*

Except in this case it's only one.

> [1]
>
> 50 === added file 'src/maasserver/tests/test_api_networks.py'
> [...]
> 83 + def test_list_returns_network(self):
>
> I put a break point in this test and look:
>
> (Pdb) parsed_result
> [{u'description': u'tjBXqFQ7vK', u'ip': u'217.187.192.0', u'_state':
> u'<django.db.models.base.ModelState object at 0x962acd0>', u'netmask':
> u'255.255.192.0', u'vlan_tag': None, u'id': 1, u'name': u'6THuCI'}]
>
> Django adds clutter to the objects (I'm talking about the '_state' field) so
> we need to explicitly list the fields that we want to return.

That goes in NetworkHandler.fields I believe, which is in one of the later branches in this series. I'll add it there.

> [2]
>
> 50 === added file 'src/maasserver/tests/test_api_networks.py'
> [...]
> 93 + (
> 94 + original_network.name,
> 95 + original_network.ip,
> 96 + original_network.netmask,
> 97 + original_network.vlan_tag,
> 98 + original_network.description,
> 99 + ),
> 100 + (
> 101 + returned_network['name'],
> 102 + returned_network['ip'],
> 103 + returned_network['netmask'],
> 104 + returned_network['vlan_tag'],
> 105 + returned_network['description'],
> 106 + ))
>
> Couldn't this benefit from self.assertAttributes?

The same words went through my head, believe me! The problem is that in this case, the object has the expected values and the dict has the tested values.

> [3]
>
> 50 === added file 'src/maasserver/tests/test_api_networks.py'
> [...]
> 93 + (
> 94 + original_network.name,
> 95 + original_network.ip,
> 96 + original_network.netmask,
> 97 + original_network.vlan_tag,
> 98 + original_network.description,
> 99 + ),
> 100 + (
> 101 + returned_network['name'],
> 102 + returned_network['ip'],
> 103 + returned_network['netmask'],
> 104 + returned_network['vlan_tag'],
> 105 + returned_network['description'],
> 106 + ))
>
> There is a bug: we should also have a 'resource_uri' field.

Yeah, I added that in one of the other branches.

Revision history for this message
Raphaël Badin (rvb) wrote :

> > [0]
> >
> > 50 === added file 'src/maasserver/tests/test_api_networks.py'
> > [...]
> > 83 + def test_list_returns_network(self):
> >
> > The name of this test is strange. One would expect
> > test_list_returns_network*s*
>
> Except in this case it's only one.

Well, I'd say it's a list of networks… with only one network. For the casual reader, test_list_returns_network looks weird.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

OK, I renamed the test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2014-02-07 17:14:08 +0000
+++ src/maasserver/api.py 2014-02-10 11:22:04 +0000
@@ -69,6 +69,7 @@
69 "FilesHandler",69 "FilesHandler",
70 "get_oauth_token",70 "get_oauth_token",
71 "MaasHandler",71 "MaasHandler",
72 "NetworksHandler",
72 "NodeGroupHandler",73 "NodeGroupHandler",
73 "NodeGroupsHandler",74 "NodeGroupsHandler",
74 "NodeGroupInterfaceHandler",75 "NodeGroupInterfaceHandler",
@@ -193,6 +194,7 @@
193 DHCPLease,194 DHCPLease,
194 FileStorage,195 FileStorage,
195 MACAddress,196 MACAddress,
197 Network,
196 Node,198 Node,
197 NodeGroup,199 NodeGroup,
198 NodeGroupInterface,200 NodeGroupInterface,
@@ -2590,11 +2592,10 @@
2590class ZoneHandler(OperationsHandler):2592class ZoneHandler(OperationsHandler):
2591 """Manage a physical zone.2593 """Manage a physical zone.
25922594
2593 Any node may optionally be assigned to a physical zone, or "zone" for2595 Any node is in a physical zone, or "zone" for short. The meaning of a
2594 short. The meaning of a physical zone is up to you: it could identify2596 physical zone is up to you: it could identify e.g. a server rack, a
2595 e.g. a server rack, a network, or a data centre. Users can then allocate2597 network, or a data centre. Users can then allocate nodes from specific
2596 nodes from specific physical zones, to suit their redundancy or2598 physical zones, to suit their redundancy or performance requirements.
2597 performance requirements.
25982599
2599 This functionality is only available to administrators. Other users can2600 This functionality is only available to administrators. Other users can
2600 view physical zones, but not modify them.2601 view physical zones, but not modify them.
@@ -2665,3 +2666,12 @@
2665 Get a listing of all the physical zones.2666 Get a listing of all the physical zones.
2666 """2667 """
2667 return Zone.objects.all().order_by('name')2668 return Zone.objects.all().order_by('name')
2669
2670
2671class NetworksHandler(OperationsHandler):
2672 """API for networks."""
2673 create = update = delete = None
2674
2675 def read(self, request):
2676 """List networks."""
2677 return Network.objects.all().order_by('name')
26682678
=== added file 'src/maasserver/tests/test_api_networks.py'
--- src/maasserver/tests/test_api_networks.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_api_networks.py 2014-02-10 11:22:04 +0000
@@ -0,0 +1,70 @@
1# Copyright 2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for networks API."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12str = None
13
14__metaclass__ = type
15__all__ = []
16
17import httplib
18import json
19
20from django.core.urlresolvers import reverse
21from maasserver.testing.api import APITestCase
22from maasserver.testing.factory import factory
23
24
25class TestNetworksAPI(APITestCase):
26
27 def test_handler_path(self):
28 self.assertEqual('/api/1.0/networks/', reverse('networks_handler'))
29
30 def test_list_returns_networks(self):
31 original_network = factory.make_network()
32
33 response = self.client.get(reverse('networks_handler'))
34 self.assertEqual(httplib.OK, response.status_code, response.content)
35
36 parsed_result = json.loads(response.content)
37 self.assertEqual(1, len(parsed_result))
38 [returned_network] = parsed_result
39 self.assertEqual(
40 (
41 original_network.name,
42 original_network.ip,
43 original_network.netmask,
44 original_network.vlan_tag,
45 original_network.description,
46 ),
47 (
48 returned_network['name'],
49 returned_network['ip'],
50 returned_network['netmask'],
51 returned_network['vlan_tag'],
52 returned_network['description'],
53 ))
54
55 def test_list_returns_empty_if_no_networks(self):
56 response = self.client.get(reverse('networks_handler'))
57 self.assertEqual(httplib.OK, response.status_code, response.content)
58 self.assertEqual([], json.loads(response.content))
59
60 def test_list_sorts_by_name(self):
61 original_names = [factory.make_name('net').lower() for _ in range(3)]
62 for name in original_names:
63 factory.make_network(name=name)
64
65 response = self.client.get(reverse('networks_handler'))
66 self.assertEqual(httplib.OK, response.status_code, response.content)
67
68 self.assertEqual(
69 sorted(original_names),
70 [network['name'] for network in json.loads(response.content)])
071
=== modified file 'src/maasserver/urls_api.py'
--- src/maasserver/urls_api.py 2013-12-13 11:43:26 +0000
+++ src/maasserver/urls_api.py 2014-02-10 11:22:04 +0000
@@ -1,4 +1,4 @@
1# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the1# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""URL API routing configuration."""4"""URL API routing configuration."""
@@ -29,6 +29,7 @@
29 FileHandler,29 FileHandler,
30 FilesHandler,30 FilesHandler,
31 MaasHandler,31 MaasHandler,
32 NetworksHandler,
32 NodeGroupHandler,33 NodeGroupHandler,
33 NodeGroupInterfaceHandler,34 NodeGroupInterfaceHandler,
34 NodeGroupInterfacesHandler,35 NodeGroupInterfacesHandler,
@@ -57,6 +58,7 @@
57account_handler = RestrictedResource(AccountHandler, authentication=api_auth)58account_handler = RestrictedResource(AccountHandler, authentication=api_auth)
58files_handler = RestrictedResource(FilesHandler, authentication=api_auth)59files_handler = RestrictedResource(FilesHandler, authentication=api_auth)
59file_handler = RestrictedResource(FileHandler, authentication=api_auth)60file_handler = RestrictedResource(FileHandler, authentication=api_auth)
61networks_handler = RestrictedResource(NetworksHandler, authentication=api_auth)
60node_handler = RestrictedResource(NodeHandler, authentication=api_auth)62node_handler = RestrictedResource(NodeHandler, authentication=api_auth)
61nodes_handler = RestrictedResource(NodesHandler, authentication=api_auth)63nodes_handler = RestrictedResource(NodesHandler, authentication=api_auth)
62node_mac_handler = RestrictedResource(NodeMacHandler, authentication=api_auth)64node_mac_handler = RestrictedResource(NodeMacHandler, authentication=api_auth)
@@ -126,6 +128,7 @@
126 nodegroupinterfaces_handler, name='nodegroupinterfaces_handler'),128 nodegroupinterfaces_handler, name='nodegroupinterfaces_handler'),
127 url(r'^nodegroups/(?P<uuid>[^/]+)/interfaces/(?P<interface>[^/]+)/$',129 url(r'^nodegroups/(?P<uuid>[^/]+)/interfaces/(?P<interface>[^/]+)/$',
128 nodegroupinterface_handler, name='nodegroupinterface_handler'),130 nodegroupinterface_handler, name='nodegroupinterface_handler'),
131 url(r'^networks/$', networks_handler, name='networks_handler'),
129 url(r'^files/$', files_handler, name='files_handler'),132 url(r'^files/$', files_handler, name='files_handler'),
130 url(r'^files/(?P<filename>.+)/$', file_handler, name='file_handler'),133 url(r'^files/(?P<filename>.+)/$', file_handler, name='file_handler'),
131 url(r'^account/$', account_handler, name='account_handler'),134 url(r'^account/$', account_handler, name='account_handler'),