Merge lp:~jtv/maas/mac-claim_static_ips-plural 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: 2740
Proposed branch: lp:~jtv/maas/mac-claim_static_ips-plural
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 317 lines (+55/-48)
6 files modified
src/maasserver/api/api.py (+7/-6)
src/maasserver/api/tests/test_node.py (+3/-3)
src/maasserver/models/macaddress.py (+19/-14)
src/maasserver/models/node.py (+2/-2)
src/maasserver/models/tests/test_macaddress.py (+16/-15)
src/maasserver/models/tests/test_node.py (+8/-8)
To merge this branch: bzr merge lp:~jtv/maas/mac-claim_static_ips-plural
Reviewer Review Type Date Requested Status
Graham Binns (community) Approve
Review via email: mp+231165@code.launchpad.net

Commit message

With IPv6, MACAddress.claim_static_ip can allocate more than one address, so rename it to claim_static_ips and make it return a list.

Description of the change

This does not make the actual change where a MACAddress can be connected to multiple cluster interfaces, and so allocate multiple static addresses. I'll leave that to a separate branch, although a helper for identifying those cluster interfaces is already up for review.

On the other hand, the branch does take care of the propagated implications: we no longer do things with the address if we get one, we do things for every address in the list.

Jeroen

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

I see Graham already approved, but I have some suggestions for improvements inline!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/api.py'
2--- src/maasserver/api/api.py 2014-08-18 04:41:45 +0000
3+++ src/maasserver/api/api.py 2014-08-18 09:56:11 +0000
4@@ -474,7 +474,7 @@
5 assign the sticky IP address. If not passed, defaults to the
6 primary MAC for the node.
7 :param requested_address: Optional IP address to claim. Must be in
8- the range defined on the cluster interface to which the context
9+ the range defined on a cluster interface to which the context
10 MAC is related, or 403 Forbidden is returned. If the requested
11 address is unavailable for use, 404 Not Found is returned.
12
13@@ -500,11 +500,12 @@
14 raise MAASAPIBadRequest(
15 "mac_address %s not found on the node" % raw_mac)
16 requested_address = request.POST.get('requested_address', None)
17- sticky_ip = mac_address.claim_static_ip(
18+ sticky_ips = mac_address.claim_static_ips(
19 alloc_type=IPADDRESS_TYPE.STICKY,
20 requested_address=requested_address)
21 maaslog.info(
22- "%s: Sticky IP address %s allocated", node.hostname, sticky_ip.ip)
23+ "%s: Sticky IP address(es) allocated: %s", node.hostname,
24+ ', '.join(allocation.ip for allocation in sticky_ips))
25 return node
26
27 @operation(idempotent=False)
28@@ -1966,9 +1967,9 @@
29 def reserve(self, request):
30 """Reserve an IP address for use outside of MAAS.
31
32- Returns an IP adddress for which MAAS will not allow any of its
33- known devices and Nodes to use; it is free for use by the requesting
34- user until released by the user.
35+ Returns an IP adddress which MAAS will not allow any of its known
36+ devices and Nodes to use; it is free for use by the requesting user
37+ until released by the user.
38
39 :param network: CIDR representation of the network on which the IP
40 reservation is required. e.g. 10.1.2.0/24
41
42=== modified file 'src/maasserver/api/tests/test_node.py'
43--- src/maasserver/api/tests/test_node.py 2014-08-18 04:41:45 +0000
44+++ src/maasserver/api/tests/test_node.py 2014-08-18 09:56:11 +0000
45@@ -1011,7 +1011,7 @@
46 def test_claim_sticky_ip_address_returns_existing_if_already_exists(self):
47 self.become_admin()
48 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
49- existing_ip = node.get_primary_mac().claim_static_ip(
50+ [existing_ip] = node.get_primary_mac().claim_static_ips(
51 alloc_type=IPADDRESS_TYPE.STICKY)
52 response = self.client.post(
53 self.get_node_uri(node), {'op': 'claim_sticky_ip_address'})
54@@ -1029,7 +1029,7 @@
55 random_alloc_type = factory.pick_enum(
56 IPADDRESS_TYPE,
57 but_not=[IPADDRESS_TYPE.STICKY, IPADDRESS_TYPE.USER_RESERVED])
58- node.get_primary_mac().claim_static_ip(alloc_type=random_alloc_type)
59+ node.get_primary_mac().claim_static_ips(alloc_type=random_alloc_type)
60 response = self.client.post(
61 self.get_node_uri(node), {'op': 'claim_sticky_ip_address'})
62 self.assertEqual(
63@@ -1128,7 +1128,7 @@
64 # Allocate an IP to one of the nodes.
65 requested_address = IPAddress(ngi.static_ip_range_low) + 1
66 requested_address = requested_address.format()
67- other_node.get_primary_mac().claim_static_ip(
68+ other_node.get_primary_mac().claim_static_ips(
69 requested_address=requested_address)
70
71 # Use the API to try to duplicate the same IP on the other node.
72
73=== modified file 'src/maasserver/models/macaddress.py'
74--- src/maasserver/models/macaddress.py 2014-08-15 22:48:42 +0000
75+++ src/maasserver/models/macaddress.py 2014-08-18 09:56:11 +0000
76@@ -1,4 +1,4 @@
77-# Copyright 2012 Canonical Ltd. This software is licensed under the
78+# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
79 # GNU Affero General Public License version 3 (see the file LICENSE).
80
81 """MACAddress model and friends."""
82@@ -89,9 +89,13 @@
83 """Return networks to which this MAC is connected, sorted by name."""
84 return self.networks.all().order_by('name')
85
86- def claim_static_ip(self, alloc_type=IPADDRESS_TYPE.AUTO,
87- requested_address=None):
88- """Assign a static IP to this MAC.
89+ def claim_static_ips(self, alloc_type=IPADDRESS_TYPE.AUTO,
90+ requested_address=None):
91+ """Assign static IP addresses to this MAC.
92+
93+ Allocates one address per managed cluster interface connected to this
94+ MAC. Typically this will be either just one IPv4 address, or an IPv4
95+ address and an IPv6 address.
96
97 It is the caller's responsibility to create a celery Task that will
98 write the dhcp host. It is not done here because celery doesn't
99@@ -101,13 +105,14 @@
100 :param alloc_type: See :class:`StaticIPAddress`.alloc_type.
101 This parameter musn't be IPADDRESS_TYPE.USER_RESERVED.
102 :param requested_address: Optional IP address to claim. Must be in
103- the range defined on the cluster interface to which this MACAddress
104- is related.
105- :return: A :class:`StaticIPAddress` object. Returns None if
106+ the range defined on a cluster interface to which this MACAddress
107+ is related. If given, no allocations will be made on any other
108+ cluster interfaces the MAC may be connected to.
109+ :return: A list of :class:`StaticIPAddress`. Returns empty if
110 the cluster_interface is not yet known, or the
111 static_ip_range_low/high values values are not set on the
112- cluster_interface. If an IP already exists for this type, it
113- is always returned with no further allocation.
114+ cluster_interface. If an IP address was already allocated, the
115+ function will return it rather than allocate a new one.
116 :raises: StaticIPAddressExhaustion if there are not enough IPs left.
117 :raises: StaticIPAddressTypeClash if an IP already exists with a
118 different type.
119@@ -129,20 +134,20 @@
120
121 # Check to see if an IP with the same type already exists.
122 try:
123- return StaticIPAddress.objects.get(
124- macaddress=self, alloc_type=alloc_type)
125+ return [StaticIPAddress.objects.get(
126+ macaddress=self, alloc_type=alloc_type)]
127 except StaticIPAddress.DoesNotExist:
128 pass
129
130 if self.cluster_interface is None:
131 # We need to know this to allocate an IP, so return nothing.
132- return None
133+ return []
134
135 low = self.cluster_interface.static_ip_range_low
136 high = self.cluster_interface.static_ip_range_high
137 if not low or not high:
138 # low/high can be None or blank if not defined yet.
139- return None
140+ return []
141
142 # Avoid circular imports.
143 from maasserver.models import (
144@@ -152,4 +157,4 @@
145 sip = StaticIPAddress.objects.allocate_new(
146 low, high, alloc_type, requested_address=requested_address)
147 MACStaticIPAddressLink(mac_address=self, ip_address=sip).save()
148- return sip
149+ return [sip]
150
151=== modified file 'src/maasserver/models/node.py'
152--- src/maasserver/models/node.py 2014-08-18 04:41:45 +0000
153+++ src/maasserver/models/node.py 2014-08-18 09:56:11 +0000
154@@ -701,7 +701,7 @@
155 macs = self.mac_addresses_on_managed_interfaces()
156 for mac in macs:
157 try:
158- sip = mac.claim_static_ip()
159+ sips = mac.claim_static_ips()
160 except StaticIPAddressTypeClash:
161 # There's already a non-AUTO IP, so nothing to do.
162 continue
163@@ -709,7 +709,7 @@
164 # defined, which will be the case when migrating from older
165 # versions of the code. If it is None we just ignore this
166 # MAC.
167- if sip is not None:
168+ for sip in sips:
169 tasks.append(self._create_hostmap_task(mac, sip))
170 maaslog.info(
171 "%s: Claimed static IP %s on %s", self.hostname,
172
173=== modified file 'src/maasserver/models/tests/test_macaddress.py'
174--- src/maasserver/models/tests/test_macaddress.py 2014-08-13 21:49:35 +0000
175+++ src/maasserver/models/tests/test_macaddress.py 2014-08-18 09:56:11 +0000
176@@ -85,55 +85,56 @@
177
178 class TestMACAddressForStaticIPClaiming(MAASServerTestCase):
179
180- def test_claim_static_ip_returns_none_if_no_cluster_interface(self):
181+ def test_claim_statics_ip_returns_empty_if_no_cluster_interface(self):
182 # If mac.cluster_interface is None, we can't allocate any IP.
183 mac = factory.make_mac_address()
184- self.assertIsNone(mac.claim_static_ip())
185+ self.assertEquals([], mac.claim_static_ips())
186
187- def test_claim_static_ip_reserves_an_ip_address(self):
188+ def test_claim_static_ips_reserves_an_ip_address(self):
189 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
190 mac = node.get_primary_mac()
191- claimed_ip = mac.claim_static_ip()
192+ [claimed_ip] = mac.claim_static_ips()
193 self.assertIsInstance(claimed_ip, StaticIPAddress)
194 self.assertNotEqual([], list(node.static_ip_addresses()))
195 self.assertEqual(
196 IPADDRESS_TYPE.AUTO, StaticIPAddress.objects.all()[0].alloc_type)
197
198- def test_claim_static_ip_sets_type_as_required(self):
199+ def test_claim_static_ips_sets_type_as_required(self):
200 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
201 mac = node.get_primary_mac()
202- claimed_ip = mac.claim_static_ip(alloc_type=IPADDRESS_TYPE.STICKY)
203+ [claimed_ip] = mac.claim_static_ips(alloc_type=IPADDRESS_TYPE.STICKY)
204 self.assertEqual(IPADDRESS_TYPE.STICKY, claimed_ip.alloc_type)
205
206- def test_claim_static_ip_returns_none_if_no_static_range_defined(self):
207+ def test_claim_static_ips_returns_none_if_no_static_range_defined(self):
208 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
209 mac = node.get_primary_mac()
210 mac.cluster_interface.static_ip_range_low = None
211 mac.cluster_interface.static_ip_range_high = None
212- self.assertIsNone(mac.claim_static_ip())
213+ self.assertEqual([], mac.claim_static_ips())
214
215- def test_claim_static_ip_raises_if_clashing_type(self):
216+ def test_claim_static_ips_raises_if_clashing_type(self):
217 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
218 mac = node.get_primary_mac()
219 iptype = factory.pick_enum(
220 IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED])
221 iptype2 = factory.pick_enum(IPADDRESS_TYPE, but_not=[iptype])
222- mac.claim_static_ip(alloc_type=iptype)
223+ mac.claim_static_ips(alloc_type=iptype)
224 self.assertRaises(
225 StaticIPAddressTypeClash,
226- mac.claim_static_ip, alloc_type=iptype2)
227+ mac.claim_static_ips, alloc_type=iptype2)
228
229- def test_claim_static_ip_returns_existing_if_claiming_same_type(self):
230+ def test_claim_static_ips_returns_existing_if_claiming_same_type(self):
231 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
232 mac = node.get_primary_mac()
233 iptype = factory.pick_enum(
234 IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED])
235- ip = mac.claim_static_ip(alloc_type=iptype)
236+ [ip] = mac.claim_static_ips(alloc_type=iptype)
237 self.assertEqual(
238- ip, mac.claim_static_ip(alloc_type=iptype))
239+ [ip], mac.claim_static_ips(alloc_type=iptype))
240
241 def test_passes_requested_ip(self):
242 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
243 mac = node.get_primary_mac()
244 ip = node.get_primary_mac().cluster_interface.static_ip_range_high
245- self.assertEqual(ip, mac.claim_static_ip(requested_address=ip).ip)
246+ [allocation] = mac.claim_static_ips(requested_address=ip)
247+ self.assertEqual(ip, allocation.ip)
248
249=== modified file 'src/maasserver/models/tests/test_node.py'
250--- src/maasserver/models/tests/test_node.py 2014-08-18 04:41:45 +0000
251+++ src/maasserver/models/tests/test_node.py 2014-08-18 09:56:11 +0000
252@@ -326,7 +326,7 @@
253 primary_mac = node.get_primary_mac()
254 random_alloc_type = factory.pick_enum(
255 IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED])
256- primary_mac.claim_static_ip(alloc_type=random_alloc_type)
257+ primary_mac.claim_static_ips(alloc_type=random_alloc_type)
258 node.delete()
259 self.assertItemsEqual([], StaticIPAddress.objects.all())
260
261@@ -335,7 +335,7 @@
262 self.patch(Omshell, 'remove')
263 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
264 primary_mac = node.get_primary_mac()
265- primary_mac.claim_static_ip(alloc_type=IPADDRESS_TYPE.STICKY)
266+ primary_mac.claim_static_ips(alloc_type=IPADDRESS_TYPE.STICKY)
267 node.delete()
268 self.assertEqual(
269 ['provisioningserver.tasks.remove_dhcp_host_map'],
270@@ -649,17 +649,17 @@
271 user = factory.make_user()
272 node = factory.make_node_with_mac_attached_to_nodegroupinterface(
273 owner=user, status=NODE_STATUS.ALLOCATED)
274- sip = node.get_primary_mac().claim_static_ip()
275+ sips = node.get_primary_mac().claim_static_ips()
276 delete_static_host_maps = self.patch(node, 'delete_static_host_maps')
277 node.release()
278- expected = [sip.ip.format()]
279+ expected = [sip.ip.format() for sip in sips]
280 self.assertThat(delete_static_host_maps, MockCalledOnceWith(expected))
281
282 def test_delete_static_host_maps(self):
283 user = factory.make_user()
284 node = factory.make_node_with_mac_attached_to_nodegroupinterface(
285 owner=user, status=NODE_STATUS.ALLOCATED)
286- sip = node.get_primary_mac().claim_static_ip()
287+ [sip] = node.get_primary_mac().claim_static_ips()
288 self.patch(Omshell, 'remove')
289 set_call = self.patch(celery.canvas.Signature, 'set')
290 node.delete_static_host_maps([sip.ip.format()])
291@@ -1476,7 +1476,7 @@
292 self.celery.tasks[0]['kwargs']['mac_address'],
293 ))
294
295- def test_start_nodes_does_not_claim_static_ip_unless_allocated(self):
296+ def test_start_nodes_does_not_claim_static_ips_unless_allocated(self):
297 user = factory.make_user()
298 node = factory.make_node_with_mac_attached_to_nodegroupinterface(
299 owner=user, power_type='ether_wake')
300@@ -1747,7 +1747,7 @@
301 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
302 self.patch(
303 MACAddress,
304- 'claim_static_ip').side_effect = StaticIPAddressExhaustion
305+ 'claim_static_ips').side_effect = StaticIPAddressExhaustion
306 deallocate_call = self.patch(
307 StaticIPAddressManager, 'deallocate_by_node')
308 self.assertRaises(StaticIPAddressExhaustion, node.claim_static_ips)
309@@ -1772,7 +1772,7 @@
310 def test_claim_static_ips_creates_no_tasks_if_existing_IP(self):
311 node = factory.make_node_with_mac_attached_to_nodegroupinterface()
312 primary_mac = node.get_primary_mac()
313- primary_mac.claim_static_ip(alloc_type=IPADDRESS_TYPE.STICKY)
314+ primary_mac.claim_static_ips(alloc_type=IPADDRESS_TYPE.STICKY)
315 ngi = factory.make_node_group_interface(
316 node.nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP)
317 second_mac = factory.make_mac_address(node=node, cluster_interface=ngi)