Merge lp:~julian-edwards/maas/request-user-ip 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: 2953
Proposed branch: lp:~julian-edwards/maas/request-user-ip
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 145 lines (+57/-5)
2 files modified
src/maasserver/api/ip_addresses.py (+18/-4)
src/maasserver/api/tests/test_ipaddresses.py (+39/-1)
To merge this branch: bzr merge lp:~julian-edwards/maas/request-user-ip
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+234248@code.launchpad.net

Commit message

Allow API users to optionally request specific IP addresses when requesting USER_RESERVED static IP addresses. Previously a random one was always allocated.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

I can see why you might return an HTTP status code of Forbidden for an address that wasn't in the right range, but Not Found for an address that's already taken seems strange. Isn't this a classic case for the Conflict error?

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

On Thursday 11 Sep 2014 05:42:49 you wrote:
> Review: Approve
>
> I can see why you might return an HTTP status code of Forbidden for an
> address that wasn't in the right range, but Not Found for an address that's
> already taken seems strange. Isn't this a classic case for the Conflict
> error?

I am following what I did in the similar change in the claim_sticky_ip stuff.
There is a good reason, conflict is wrong IMO. If I can find the original MP
I'll show you later.

Thanks for the review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/ip_addresses.py'
2--- src/maasserver/api/ip_addresses.py 2014-08-19 02:14:31 +0000
3+++ src/maasserver/api/ip_addresses.py 2014-09-11 03:26:49 +0000
4@@ -21,7 +21,10 @@
5 operation,
6 OperationsHandler,
7 )
8-from maasserver.api.utils import get_mandatory_param
9+from maasserver.api.utils import (
10+ get_mandatory_param,
11+ get_optional_param,
12+ )
13 from maasserver.enum import (
14 IPADDRESS_TYPE,
15 NODEGROUP_STATUS,
16@@ -32,6 +35,10 @@
17 StaticIPAddress,
18 )
19 import netaddr
20+from provisioningserver.logger import get_maas_logger
21+
22+
23+maaslog = get_maas_logger("ip_addresses")
24
25
26 class IPAddressesHandler(OperationsHandler):
27@@ -46,17 +53,20 @@
28 def resource_uri(cls, *args, **kwargs):
29 return ('ipaddresses_handler', [])
30
31- def claim_ip(self, user, interface):
32+ def claim_ip(self, user, interface, requested_address):
33 """Attempt to get a USER_RESERVED StaticIPAddress for `user` on
34 `interface`.
35
36 :raises StaticIPAddressExhaustion: If no IPs available.
37 """
38- return StaticIPAddress.objects.allocate_new(
39+ sip = StaticIPAddress.objects.allocate_new(
40 range_low=interface.static_ip_range_low,
41 range_high=interface.static_ip_range_high,
42 alloc_type=IPADDRESS_TYPE.USER_RESERVED,
43+ requested_address=requested_address,
44 user=user)
45+ maaslog.info("User %s was allocated IP %s", user.username, sip.ip)
46+ return sip
47
48 @operation(idempotent=False)
49 def reserve(self, request):
50@@ -71,6 +81,8 @@
51 :type network: unicode
52 """
53 network = get_mandatory_param(request.POST, "network")
54+ requested_address = get_optional_param(
55+ request.POST, "requested_address")
56 # Validate the passed network.
57 try:
58 valid_network = netaddr.IPNetwork(network)
59@@ -87,7 +99,8 @@
60 for interface in interfaces:
61 if valid_network == interface.network:
62 # Winner winner chicken dinner.
63- return self.claim_ip(request.user, interface)
64+ return self.claim_ip(
65+ request.user, interface, requested_address)
66 raise MAASAPIBadRequest("No network found matching %s" % network)
67
68 @operation(idempotent=False)
69@@ -101,6 +114,7 @@
70 staticaddress = get_object_or_404(
71 StaticIPAddress, ip=ip, user=request.user)
72 staticaddress.deallocate()
73+ maaslog.info("User %s released IP %s", request.user.username, ip)
74
75 def read(self, request):
76 """List IPAddresses.
77
78=== modified file 'src/maasserver/api/tests/test_ipaddresses.py'
79--- src/maasserver/api/tests/test_ipaddresses.py 2014-09-05 16:38:32 +0000
80+++ src/maasserver/api/tests/test_ipaddresses.py 2014-09-11 03:26:49 +0000
81@@ -26,6 +26,8 @@
82 from maasserver.testing.api import APITestCase
83 from maasserver.testing.factory import factory
84 from maasserver.testing.orm import reload_object
85+from netaddr import IPAddress
86+from testtools.matchers import Equals
87
88
89 class TestNetworksAPI(APITestCase):
90@@ -34,11 +36,13 @@
91 cluster = factory.make_NodeGroup(status=status, **kwargs)
92 return factory.make_NodeGroupInterface(cluster)
93
94- def post_reservation_request(self, net):
95+ def post_reservation_request(self, net, requested_address=None):
96 params = {
97 'op': 'reserve',
98 'network': unicode(net),
99 }
100+ if requested_address is not None:
101+ params["requested_address"] = requested_address
102 return self.client.post(reverse('ipaddresses_handler'), params)
103
104 def post_release_request(self, ip):
105@@ -103,6 +107,40 @@
106 "Invalid network parameter %s" % net,
107 response.content)
108
109+ def test_POST_reserve_creates_requested_address(self):
110+ interface = self.make_interface()
111+ net = interface.network
112+ ip_in_network = interface.static_ip_range_low
113+ response = self.post_reservation_request(net, ip_in_network)
114+ self.assertEqual(httplib.OK, response.status_code, response.content)
115+ returned_address = json.loads(response.content)
116+ [staticipaddress] = StaticIPAddress.objects.all()
117+ self.expectThat(
118+ returned_address["alloc_type"],
119+ Equals(IPADDRESS_TYPE.USER_RESERVED))
120+ self.expectThat(returned_address["ip"], Equals(ip_in_network))
121+ self.expectThat(staticipaddress.ip, Equals(ip_in_network))
122+
123+ def test_POST_reserve_requested_address_detects_in_use_address(self):
124+ interface = self.make_interface()
125+ net = interface.network
126+ ip_in_network = interface.static_ip_range_low
127+ response = self.post_reservation_request(net, ip_in_network)
128+ self.assertEqual(httplib.OK, response.status_code, response.content)
129+ # Do same request again and check it is rejected.
130+ response = self.post_reservation_request(net, ip_in_network)
131+ self.assertEqual(
132+ httplib.NOT_FOUND, response.status_code, response.content)
133+
134+ def test_POST_reserve_requested_address_detects_out_of_range_addr(self):
135+ interface = self.make_interface()
136+ net = interface.network
137+ ip_not_in_network = unicode(
138+ IPAddress(interface.static_ip_range_low) - 1)
139+ response = self.post_reservation_request(net, ip_not_in_network)
140+ self.assertEqual(
141+ httplib.FORBIDDEN, response.status_code, response.content)
142+
143 def test_GET_returns_ipaddresses(self):
144 original_ipaddress = factory.make_StaticIPAddress(
145 user=self.logged_in_user)