Merge lp:~julian-edwards/maas/always-send-leases into lp:~maas-committers/maas/trunk
- always-send-leases
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Julian Edwards | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2938 | ||||
Proposed branch: | lp:~julian-edwards/maas/always-send-leases | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
602 lines (+220/-199) 7 files modified
src/maasserver/api/node_groups.py (+2/-58) src/maasserver/api/tests/test_nodegroup.py (+0/-136) src/maasserver/models/macaddress.py (+56/-1) src/maasserver/models/node.py (+9/-0) src/maasserver/models/tests/test_macaddress.py (+135/-1) src/maasserver/models/tests/test_node.py (+17/-2) src/maasserver/rpc/leases.py (+1/-1) |
||||
To merge this branch: | bzr merge lp:~julian-edwards/maas/always-send-leases | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+234052@code.launchpad.net |
Commit message
Attempt to populate the cluster_interface when a MAC is added to a node. This avoids the problem where a lease is already present after a node and/or MAC address is added because that linkage was previously only attempted at lease upload time. That's problematic because leases are not re-sent to the region unless changes are noticed.
Description of the change
Julian Edwards (julian-edwards) wrote : | # |
Jeroen T. Vermeulen (jtv) wrote : | # |
Just the changes in the imports are enough to suggest that you moved update_
It seems heavy-handed to run all of the cluster's leases through update_
Julian Edwards (julian-edwards) wrote : | # |
On Wednesday 10 September 2014 06:24:16 you wrote:
> Review: Approve
>
> Just the changes in the imports are enough to suggest that you moved
> update_
> to move though?
Ah good point, I forgot those, thanks.
> It seems heavy-handed to run all of the cluster's leases through
> update_
> node... Would it not be enough to pass just the leases for that one MAC?
I thought long and hard about this, and decided that it might not be a bad
idea to let them all refresh. Let's revisit if it's obviously becoming a
problem.
Thanks for the review!
Jeroen T. Vermeulen (jtv) wrote : | # |
Careful: if you update other MACs that aren't obviously related, you may end up hiding some other, similar bug from our testing.
Julian Edwards (julian-edwards) wrote : | # |
On Wednesday 10 September 2014 06:35:03 you wrote:
> Careful: if you update other MACs that aren't obviously related, you may end
> up hiding some other, similar bug from our testing.
That's a fair point. I'm going to land this now anyway so we can unfuck the
CI suite. Let's have a think about this at a more leisurely pace and see what
needs to be changed.
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~julian-edwards/maas/always-send-leases into lp:maas failed. Below is the output from the failed tests.
Ign http://
Hit http://
Hit http://
Ign http://
Ign http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Ign http://
Ign http://
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === modified file 'src/maasserver/api/node_groups.py' |
2 | --- src/maasserver/api/node_groups.py 2014-09-09 11:21:41 +0000 |
3 | +++ src/maasserver/api/node_groups.py 2014-09-10 07:41:56 +0000 |
4 | @@ -53,19 +53,14 @@ |
5 | NodeGroupDefineForm, |
6 | NodeGroupEdit, |
7 | ) |
8 | -from maasserver.models import ( |
9 | - MACAddress, |
10 | - Network, |
11 | - Node, |
12 | - NodeGroup, |
13 | - ) |
14 | +from maasserver.models.node import Node |
15 | +from maasserver.models.nodegroup import NodeGroup |
16 | from maasserver.models.nodeprobeddetails import get_probed_details |
17 | from maasserver.utils import ( |
18 | build_absolute_uri, |
19 | get_local_cluster_UUID, |
20 | ) |
21 | from maasserver.utils.orm import get_one |
22 | -import netaddr |
23 | |
24 | |
25 | DISPLAYED_NODEGROUP_FIELDS = ('uuid', 'status', 'name', 'cluster_name') |
26 | @@ -230,57 +225,6 @@ |
27 | nodegroup.save() |
28 | |
29 | |
30 | -def update_mac_cluster_interfaces(leases, cluster): |
31 | - """Calculate and store which interface a MAC is attached to.""" |
32 | - interface_ranges = {} |
33 | - # Only consider configured interfaces. |
34 | - interfaces = ( |
35 | - cluster.nodegroupinterface_set |
36 | - .exclude(ip_range_low__isnull=True) |
37 | - .exclude(ip_range_high__isnull=True) |
38 | - ) |
39 | - for interface in interfaces: |
40 | - ip_range = netaddr.IPRange( |
41 | - interface.ip_range_low, interface.ip_range_high) |
42 | - if interface.static_ip_range_low and interface.static_ip_range_high: |
43 | - static_range = netaddr.IPRange( |
44 | - interface.static_ip_range_low, interface.static_ip_range_high) |
45 | - else: |
46 | - static_range = [] |
47 | - interface_ranges[interface] = (ip_range, static_range) |
48 | - for ip, mac in leases.items(): |
49 | - try: |
50 | - mac_address = MACAddress.objects.get(mac_address=mac) |
51 | - except MACAddress.DoesNotExist: |
52 | - # Silently ignore MAC addresses that we don't know about. |
53 | - continue |
54 | - for interface, (ip_range, static_range) in interface_ranges.items(): |
55 | - ipaddress = netaddr.IPAddress(ip) |
56 | - # Set the cluster interface only if it's new/changed. |
57 | - # This is only an optimisation to prevent repeated logging. |
58 | - changed = mac_address.cluster_interface != interface |
59 | - in_range = ipaddress in ip_range or ipaddress in static_range |
60 | - if in_range and changed: |
61 | - mac_address.cluster_interface = interface |
62 | - mac_address.save() |
63 | - maaslog.info( |
64 | - "%s %s linked to cluster interface %s", |
65 | - mac_address.node.hostname, mac_address, interface.name) |
66 | - |
67 | - # Locate the Network to which this MAC belongs. |
68 | - ipnetwork = interface.network |
69 | - if ipnetwork is not None: |
70 | - try: |
71 | - network = Network.objects.get(ip=ipnetwork.ip.format()) |
72 | - network.macaddress_set.add(mac_address) |
73 | - except Network.DoesNotExist: |
74 | - pass |
75 | - |
76 | - # Cheap optimisation. No other interfaces will match, so |
77 | - # break out of the loop. |
78 | - break |
79 | - |
80 | - |
81 | class NodeGroupsHandler(OperationsHandler): |
82 | """Manage the collection of all the nodegroups in this MAAS.""" |
83 | |
84 | |
85 | === modified file 'src/maasserver/api/tests/test_nodegroup.py' |
86 | --- src/maasserver/api/tests/test_nodegroup.py 2014-09-09 07:21:43 +0000 |
87 | +++ src/maasserver/api/tests/test_nodegroup.py 2014-09-10 07:41:56 +0000 |
88 | @@ -16,24 +16,18 @@ |
89 | |
90 | import httplib |
91 | import json |
92 | -import random |
93 | from textwrap import dedent |
94 | |
95 | import bson |
96 | from django.contrib.auth.models import AnonymousUser |
97 | from django.core.urlresolvers import reverse |
98 | -from maasserver.api.node_groups import update_mac_cluster_interfaces |
99 | from maasserver.bootresources import get_simplestream_endpoint |
100 | from maasserver.enum import ( |
101 | NODEGROUP_STATUS, |
102 | NODEGROUP_STATUS_CHOICES, |
103 | - NODEGROUPINTERFACE_MANAGEMENT, |
104 | ) |
105 | -from maasserver.forms import create_Network_from_NodeGroupInterface |
106 | from maasserver.models import ( |
107 | DownloadProgress, |
108 | - MACAddress, |
109 | - Network, |
110 | NodeGroup, |
111 | nodegroup as nodegroup_module, |
112 | ) |
113 | @@ -60,31 +54,15 @@ |
114 | NodeResult, |
115 | ) |
116 | from mock import Mock |
117 | -import netaddr |
118 | from provisioningserver.auth import get_recorded_nodegroup_uuid |
119 | from provisioningserver.rpc.cluster import ImportBootImages |
120 | from testresources import FixtureResource |
121 | from testtools.matchers import ( |
122 | AllMatch, |
123 | Equals, |
124 | - MatchesStructure, |
125 | ) |
126 | |
127 | |
128 | -def get_random_ip_from_interface_range(interface, use_static_range=None): |
129 | - """Return a random IP from the pool available to an interface. |
130 | - |
131 | - :return: An IP address as a string.""" |
132 | - if use_static_range: |
133 | - ip_range = netaddr.IPRange( |
134 | - interface.static_ip_range_low, interface.static_ip_range_high) |
135 | - else: |
136 | - ip_range = netaddr.IPRange( |
137 | - interface.ip_range_low, interface.ip_range_high) |
138 | - chosen_ip = random.choice(ip_range) |
139 | - return unicode(chosen_ip) |
140 | - |
141 | - |
142 | class TestNodeGroupsAPI(MultipleUsersScenarios, |
143 | MAASServerTestCase): |
144 | scenarios = [ |
145 | @@ -694,117 +672,3 @@ |
146 | self.assertEqual( |
147 | httplib.FORBIDDEN, response.status_code, |
148 | explain_unexpected_response(httplib.FORBIDDEN, response)) |
149 | - |
150 | - |
151 | -class TestUpdateMacClusterInterfaces(MAASServerTestCase): |
152 | - """Tests for `update_mac_cluster_interfaces`().""" |
153 | - |
154 | - def make_cluster_with_macs_and_leases(self, use_static_range=False): |
155 | - cluster = factory.make_NodeGroup() |
156 | - mac_addresses = { |
157 | - factory.make_MACAddress(): factory.make_NodeGroupInterface( |
158 | - nodegroup=cluster) |
159 | - for i in range(4) |
160 | - } |
161 | - leases = { |
162 | - get_random_ip_from_interface_range(interface, use_static_range): ( |
163 | - mac_address.mac_address) |
164 | - for mac_address, interface in mac_addresses.viewitems() |
165 | - } |
166 | - return cluster, mac_addresses, leases |
167 | - |
168 | - def test_updates_mac_cluster_interfaces(self): |
169 | - cluster, mac_addresses, leases = ( |
170 | - self.make_cluster_with_macs_and_leases()) |
171 | - update_mac_cluster_interfaces(leases, cluster) |
172 | - results = { |
173 | - mac_address: mac_address.cluster_interface |
174 | - for mac_address in MACAddress.objects.filter( |
175 | - mac_address__in=leases.values()) |
176 | - } |
177 | - self.assertEqual(mac_addresses, results) |
178 | - |
179 | - def test_considers_static_range_when_updating_interfaces(self): |
180 | - cluster, mac_addresses, leases = ( |
181 | - self.make_cluster_with_macs_and_leases(use_static_range=True)) |
182 | - update_mac_cluster_interfaces(leases, cluster) |
183 | - results = { |
184 | - mac_address: mac_address.cluster_interface |
185 | - for mac_address in MACAddress.objects.filter( |
186 | - mac_address__in=leases.values()) |
187 | - } |
188 | - self.assertEqual(mac_addresses, results) |
189 | - |
190 | - def test_updates_network_relations(self): |
191 | - # update_mac_cluster_interfaces should also associate the mac |
192 | - # with the network on which it resides. |
193 | - cluster, mac_addresses, leases = ( |
194 | - self.make_cluster_with_macs_and_leases()) |
195 | - expected_relations = [] |
196 | - for mac, interface in mac_addresses.viewitems(): |
197 | - net = create_Network_from_NodeGroupInterface(interface) |
198 | - expected_relations.append((net, mac)) |
199 | - update_mac_cluster_interfaces(leases, cluster) |
200 | - # Doing a single giant comparison here would be unintuitive and |
201 | - # fugly, so I'm iterating. |
202 | - for net, mac in expected_relations: |
203 | - [observed_macddress] = net.macaddress_set.all() |
204 | - self.expectThat(mac, Equals(observed_macddress)) |
205 | - interface = mac_addresses[mac] |
206 | - self.expectThat( |
207 | - net, MatchesStructure.byEquality( |
208 | - default_gateway=interface.router_ip, |
209 | - netmask=interface.subnet_mask, |
210 | - )) |
211 | - |
212 | - def test_does_not_overwrite_network_with_same_name(self): |
213 | - cluster = factory.make_NodeGroup() |
214 | - ngi = factory.make_NodeGroupInterface(nodegroup=cluster) |
215 | - net1 = create_Network_from_NodeGroupInterface(ngi) |
216 | - |
217 | - other_ngi = factory.make_NodeGroupInterface(nodegroup=cluster) |
218 | - other_ngi.interface = ngi.interface |
219 | - net2 = create_Network_from_NodeGroupInterface(ngi) |
220 | - self.assertEqual(None, net2) |
221 | - self.assertItemsEqual([net1], Network.objects.all()) |
222 | - |
223 | - def test_ignores_mac_not_attached_to_cluster(self): |
224 | - cluster = factory.make_NodeGroup() |
225 | - mac_address = factory.make_MACAddress() |
226 | - leases = { |
227 | - factory.getRandomIPAddress(): mac_address.mac_address |
228 | - } |
229 | - update_mac_cluster_interfaces(leases, cluster) |
230 | - mac_address = MACAddress.objects.get( |
231 | - id=mac_address.id) |
232 | - self.assertIsNone(mac_address.cluster_interface) |
233 | - |
234 | - def test_ignores_unknown_macs(self): |
235 | - cluster = factory.make_NodeGroup() |
236 | - mac_address = factory.getRandomMACAddress() |
237 | - leases = { |
238 | - factory.getRandomIPAddress(): mac_address |
239 | - } |
240 | - # This is a test to show that update_mac_cluster_interfaces() |
241 | - # doesn't raise an Http404 when it comes across something it |
242 | - # doesn't know, hence the lack of meaningful assertions. |
243 | - update_mac_cluster_interfaces(leases, cluster) |
244 | - self.assertFalse( |
245 | - MACAddress.objects.filter(mac_address=mac_address).exists()) |
246 | - |
247 | - def test_ignores_unconfigured_interfaces(self): |
248 | - cluster = factory.make_NodeGroup() |
249 | - factory.make_NodeGroupInterface( |
250 | - nodegroup=cluster, subnet_mask='', broadcast_ip='', |
251 | - static_ip_range_low='', static_ip_range_high='', |
252 | - ip_range_low='', ip_range_high='', router_ip='', |
253 | - ip=factory.getRandomIPAddress(), |
254 | - management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED) |
255 | - mac_address = factory.getRandomMACAddress() |
256 | - leases = { |
257 | - factory.getRandomIPAddress(): mac_address |
258 | - } |
259 | - self.assertIsNone(update_mac_cluster_interfaces(leases, cluster)) |
260 | - # The real test is that update_mac_cluster_interfaces() doesn't |
261 | - # stacktrace because of the unconfigured interface (see bug |
262 | - # 1332596). |
263 | |
264 | === modified file 'src/maasserver/models/macaddress.py' |
265 | --- src/maasserver/models/macaddress.py 2014-09-09 11:06:20 +0000 |
266 | +++ src/maasserver/models/macaddress.py 2014-09-10 07:41:56 +0000 |
267 | @@ -35,9 +35,13 @@ |
268 | ) |
269 | from maasserver.models.cleansave import CleanSave |
270 | from maasserver.models.managers import BulkManager |
271 | +from maasserver.models.network import Network |
272 | from maasserver.models.nodegroupinterface import NodeGroupInterface |
273 | from maasserver.models.timestampedmodel import TimestampedModel |
274 | -from netaddr import IPAddress |
275 | +from netaddr import ( |
276 | + IPAddress, |
277 | + IPRange, |
278 | + ) |
279 | from provisioningserver.logger import get_maas_logger |
280 | |
281 | |
282 | @@ -59,6 +63,57 @@ |
283 | return None |
284 | |
285 | |
286 | +def update_mac_cluster_interfaces(leases, cluster): |
287 | + """Calculate and store which interface a MAC is attached to.""" |
288 | + interface_ranges = {} |
289 | + # Only consider configured interfaces. |
290 | + interfaces = ( |
291 | + cluster.nodegroupinterface_set |
292 | + .exclude(ip_range_low__isnull=True) |
293 | + .exclude(ip_range_high__isnull=True) |
294 | + ) |
295 | + for interface in interfaces: |
296 | + ip_range = IPRange( |
297 | + interface.ip_range_low, interface.ip_range_high) |
298 | + if interface.static_ip_range_low and interface.static_ip_range_high: |
299 | + static_range = IPRange( |
300 | + interface.static_ip_range_low, interface.static_ip_range_high) |
301 | + else: |
302 | + static_range = [] |
303 | + interface_ranges[interface] = (ip_range, static_range) |
304 | + for ip, mac in leases.items(): |
305 | + try: |
306 | + mac_address = MACAddress.objects.get(mac_address=mac) |
307 | + except MACAddress.DoesNotExist: |
308 | + # Silently ignore MAC addresses that we don't know about. |
309 | + continue |
310 | + for interface, (ip_range, static_range) in interface_ranges.items(): |
311 | + ipaddress = IPAddress(ip) |
312 | + # Set the cluster interface only if it's new/changed. |
313 | + # This is only an optimisation to prevent repeated logging. |
314 | + changed = mac_address.cluster_interface != interface |
315 | + in_range = ipaddress in ip_range or ipaddress in static_range |
316 | + if in_range and changed: |
317 | + mac_address.cluster_interface = interface |
318 | + mac_address.save() |
319 | + maaslog.info( |
320 | + "%s %s linked to cluster interface %s", |
321 | + mac_address.node.hostname, mac_address, interface.name) |
322 | + |
323 | + # Locate the Network to which this MAC belongs. |
324 | + ipnetwork = interface.network |
325 | + if ipnetwork is not None: |
326 | + try: |
327 | + network = Network.objects.get(ip=ipnetwork.ip.format()) |
328 | + network.macaddress_set.add(mac_address) |
329 | + except Network.DoesNotExist: |
330 | + pass |
331 | + |
332 | + # Cheap optimisation. No other interfaces will match, so |
333 | + # break out of the loop. |
334 | + break |
335 | + |
336 | + |
337 | class MACAddress(CleanSave, TimestampedModel): |
338 | """A `MACAddress` represents a `MAC address`_ attached to a :class:`Node`. |
339 | |
340 | |
341 | === modified file 'src/maasserver/models/node.py' |
342 | --- src/maasserver/models/node.py 2014-09-04 22:46:11 +0000 |
343 | +++ src/maasserver/models/node.py 2014-09-10 07:41:56 +0000 |
344 | @@ -78,6 +78,7 @@ |
345 | from maasserver.models.config import Config |
346 | from maasserver.models.dhcplease import DHCPLease |
347 | from maasserver.models.licensekey import LicenseKey |
348 | +from maasserver.models.macaddress import update_mac_cluster_interfaces |
349 | from maasserver.models.staticipaddress import ( |
350 | StaticIPAddress, |
351 | StaticIPAddressExhaustion, |
352 | @@ -882,6 +883,14 @@ |
353 | |
354 | mac = MACAddress(mac_address=mac_address, node=self) |
355 | mac.save() |
356 | + |
357 | + # See if there's a lease for this MAC and set its |
358 | + # cluster_interface if so. |
359 | + nodegroup_leases = { |
360 | + lease.ip: lease.mac |
361 | + for lease in DHCPLease.objects.filter(nodegroup=self.nodegroup)} |
362 | + update_mac_cluster_interfaces(nodegroup_leases, self.nodegroup) |
363 | + |
364 | return mac |
365 | |
366 | def remove_mac_address(self, mac_address): |
367 | |
368 | === modified file 'src/maasserver/models/tests/test_macaddress.py' |
369 | --- src/maasserver/models/tests/test_macaddress.py 2014-09-05 16:38:32 +0000 |
370 | +++ src/maasserver/models/tests/test_macaddress.py 2014-09-10 07:41:56 +0000 |
371 | @@ -15,6 +15,7 @@ |
372 | __all__ = [] |
373 | |
374 | from operator import attrgetter |
375 | +import random |
376 | |
377 | from django.core.exceptions import ValidationError |
378 | from maasserver.enum import ( |
379 | @@ -22,27 +23,46 @@ |
380 | NODEGROUPINTERFACE_MANAGEMENT, |
381 | ) |
382 | from maasserver.exceptions import StaticIPAddressTypeClash |
383 | +from maasserver.forms import create_Network_from_NodeGroupInterface |
384 | from maasserver.models import ( |
385 | - MACAddress, |
386 | NodeGroupInterface, |
387 | StaticIPAddress, |
388 | ) |
389 | from maasserver.models.macaddress import ( |
390 | find_cluster_interface_responsible_for_ip, |
391 | + MACAddress, |
392 | + update_mac_cluster_interfaces, |
393 | ) |
394 | +from maasserver.models.network import Network |
395 | from maasserver.testing.factory import factory |
396 | from maasserver.testing.orm import reload_object |
397 | from maasserver.testing.testcase import MAASServerTestCase |
398 | from netaddr import ( |
399 | IPAddress, |
400 | IPNetwork, |
401 | + IPRange, |
402 | ) |
403 | from testtools.matchers import ( |
404 | Equals, |
405 | HasLength, |
406 | + MatchesStructure, |
407 | ) |
408 | |
409 | |
410 | +def get_random_ip_from_interface_range(interface, use_static_range=None): |
411 | + """Return a random IP from the pool available to an interface. |
412 | + |
413 | + :return: An IP address as a string.""" |
414 | + if use_static_range: |
415 | + ip_range = IPRange( |
416 | + interface.static_ip_range_low, interface.static_ip_range_high) |
417 | + else: |
418 | + ip_range = IPRange( |
419 | + interface.ip_range_low, interface.ip_range_high) |
420 | + chosen_ip = random.choice(ip_range) |
421 | + return unicode(chosen_ip) |
422 | + |
423 | + |
424 | class MACAddressTest(MAASServerTestCase): |
425 | |
426 | def test_stores_to_database(self): |
427 | @@ -675,3 +695,117 @@ |
428 | my_mac = factory.make_MACAddress( |
429 | node=my_node, cluster_interface=my_interface) |
430 | self.assertItemsEqual([my_interface], my_mac.get_cluster_interfaces()) |
431 | + |
432 | + |
433 | +class TestUpdateMacClusterInterfaces(MAASServerTestCase): |
434 | + """Tests for `update_mac_cluster_interfaces`().""" |
435 | + |
436 | + def make_cluster_with_macs_and_leases(self, use_static_range=False): |
437 | + cluster = factory.make_NodeGroup() |
438 | + mac_addresses = { |
439 | + factory.make_MACAddress(): factory.make_NodeGroupInterface( |
440 | + nodegroup=cluster) |
441 | + for i in range(4) |
442 | + } |
443 | + leases = { |
444 | + get_random_ip_from_interface_range(interface, use_static_range): ( |
445 | + mac_address.mac_address) |
446 | + for mac_address, interface in mac_addresses.viewitems() |
447 | + } |
448 | + return cluster, mac_addresses, leases |
449 | + |
450 | + def test_updates_mac_cluster_interfaces(self): |
451 | + cluster, mac_addresses, leases = ( |
452 | + self.make_cluster_with_macs_and_leases()) |
453 | + update_mac_cluster_interfaces(leases, cluster) |
454 | + results = { |
455 | + mac_address: mac_address.cluster_interface |
456 | + for mac_address in MACAddress.objects.filter( |
457 | + mac_address__in=leases.values()) |
458 | + } |
459 | + self.assertEqual(mac_addresses, results) |
460 | + |
461 | + def test_considers_static_range_when_updating_interfaces(self): |
462 | + cluster, mac_addresses, leases = ( |
463 | + self.make_cluster_with_macs_and_leases(use_static_range=True)) |
464 | + update_mac_cluster_interfaces(leases, cluster) |
465 | + results = { |
466 | + mac_address: mac_address.cluster_interface |
467 | + for mac_address in MACAddress.objects.filter( |
468 | + mac_address__in=leases.values()) |
469 | + } |
470 | + self.assertEqual(mac_addresses, results) |
471 | + |
472 | + def test_updates_network_relations(self): |
473 | + # update_mac_cluster_interfaces should also associate the mac |
474 | + # with the network on which it resides. |
475 | + cluster, mac_addresses, leases = ( |
476 | + self.make_cluster_with_macs_and_leases()) |
477 | + expected_relations = [] |
478 | + for mac, interface in mac_addresses.viewitems(): |
479 | + net = create_Network_from_NodeGroupInterface(interface) |
480 | + expected_relations.append((net, mac)) |
481 | + update_mac_cluster_interfaces(leases, cluster) |
482 | + # Doing a single giant comparison here would be unintuitive and |
483 | + # fugly, so I'm iterating. |
484 | + for net, mac in expected_relations: |
485 | + [observed_macddress] = net.macaddress_set.all() |
486 | + self.expectThat(mac, Equals(observed_macddress)) |
487 | + interface = mac_addresses[mac] |
488 | + self.expectThat( |
489 | + net, MatchesStructure.byEquality( |
490 | + default_gateway=interface.router_ip, |
491 | + netmask=interface.subnet_mask, |
492 | + )) |
493 | + |
494 | + def test_does_not_overwrite_network_with_same_name(self): |
495 | + cluster = factory.make_NodeGroup() |
496 | + ngi = factory.make_NodeGroupInterface(nodegroup=cluster) |
497 | + net1 = create_Network_from_NodeGroupInterface(ngi) |
498 | + |
499 | + other_ngi = factory.make_NodeGroupInterface(nodegroup=cluster) |
500 | + other_ngi.interface = ngi.interface |
501 | + net2 = create_Network_from_NodeGroupInterface(ngi) |
502 | + self.assertEqual(None, net2) |
503 | + self.assertItemsEqual([net1], Network.objects.all()) |
504 | + |
505 | + def test_ignores_mac_not_attached_to_cluster(self): |
506 | + cluster = factory.make_NodeGroup() |
507 | + mac_address = factory.make_MACAddress() |
508 | + leases = { |
509 | + factory.getRandomIPAddress(): mac_address.mac_address |
510 | + } |
511 | + update_mac_cluster_interfaces(leases, cluster) |
512 | + mac_address = MACAddress.objects.get( |
513 | + id=mac_address.id) |
514 | + self.assertIsNone(mac_address.cluster_interface) |
515 | + |
516 | + def test_ignores_unknown_macs(self): |
517 | + cluster = factory.make_NodeGroup() |
518 | + mac_address = factory.getRandomMACAddress() |
519 | + leases = { |
520 | + factory.getRandomIPAddress(): mac_address |
521 | + } |
522 | + # This is a test to show that update_mac_cluster_interfaces() |
523 | + # doesn't raise an Http404 when it comes across something it |
524 | + # doesn't know, hence the lack of meaningful assertions. |
525 | + update_mac_cluster_interfaces(leases, cluster) |
526 | + self.assertFalse( |
527 | + MACAddress.objects.filter(mac_address=mac_address).exists()) |
528 | + |
529 | + def test_ignores_unconfigured_interfaces(self): |
530 | + cluster = factory.make_NodeGroup() |
531 | + factory.make_NodeGroupInterface( |
532 | + nodegroup=cluster, subnet_mask='', broadcast_ip='', |
533 | + static_ip_range_low='', static_ip_range_high='', |
534 | + ip_range_low='', ip_range_high='', router_ip='', |
535 | + ip=factory.getRandomIPAddress(), |
536 | + management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED) |
537 | + mac_address = factory.getRandomMACAddress() |
538 | + leases = { |
539 | + factory.getRandomIPAddress(): mac_address |
540 | + } |
541 | + self.assertIsNone(update_mac_cluster_interfaces(leases, cluster)) |
542 | + # The real test is that update_mac_cluster_interfaces() doesn't |
543 | + # stacktrace because of the unconfigured interface (see bug |
544 | + # 1332596). |
545 | |
546 | === modified file 'src/maasserver/models/tests/test_node.py' |
547 | --- src/maasserver/models/tests/test_node.py 2014-09-09 07:21:43 +0000 |
548 | +++ src/maasserver/models/tests/test_node.py 2014-09-10 07:41:56 +0000 |
549 | @@ -81,7 +81,7 @@ |
550 | ANY, |
551 | sentinel, |
552 | ) |
553 | -from provisioningserver.rpc import cluster |
554 | +from provisioningserver.rpc import cluster as cluster_module |
555 | from provisioningserver.rpc.exceptions import MultipleFailures |
556 | from provisioningserver.rpc.testing import ( |
557 | always_succeed_with, |
558 | @@ -273,6 +273,21 @@ |
559 | macs = MACAddress.objects.filter(node=node, mac_address=mac).count() |
560 | self.assertEqual(1, macs) |
561 | |
562 | + def test_add_mac_address_sets_cluster_interface(self): |
563 | + # If a DHCPLease exists for this mac, ensure the |
564 | + # cluster_interface is set on the basis of that lease. |
565 | + cluster = factory.make_NodeGroup() |
566 | + cluster_interface = factory.make_NodeGroupInterface(nodegroup=cluster) |
567 | + ip_in_range = cluster_interface.static_ip_range_low |
568 | + mac_address = factory.getRandomMACAddress() |
569 | + factory.make_DHCPLease( |
570 | + mac=mac_address, ip=ip_in_range, nodegroup=cluster) |
571 | + node = factory.make_Node(nodegroup=cluster) |
572 | + |
573 | + node.add_mac_address(mac_address) |
574 | + self.assertEqual( |
575 | + cluster_interface, node.get_primary_mac().cluster_interface) |
576 | + |
577 | def test_remove_mac_address(self): |
578 | mac = factory.getRandomMACAddress() |
579 | node = factory.make_Node() |
580 | @@ -1459,7 +1474,7 @@ |
581 | |
582 | def prepare_rpc_to_cluster(self, nodegroup): |
583 | protocol = self.rpc_fixture.makeCluster( |
584 | - nodegroup, cluster.CreateHostMaps, cluster.PowerOn) |
585 | + nodegroup, cluster_module.CreateHostMaps, cluster_module.PowerOn) |
586 | protocol.CreateHostMaps.side_effect = always_succeed_with({}) |
587 | protocol.PowerOn.side_effect = always_succeed_with({}) |
588 | return protocol |
589 | |
590 | === modified file 'src/maasserver/rpc/leases.py' |
591 | --- src/maasserver/rpc/leases.py 2014-09-02 02:18:28 +0000 |
592 | +++ src/maasserver/rpc/leases.py 2014-09-10 07:41:56 +0000 |
593 | @@ -17,8 +17,8 @@ |
594 | ] |
595 | |
596 | from django.shortcuts import get_object_or_404 |
597 | -from maasserver.api.node_groups import update_mac_cluster_interfaces |
598 | from maasserver.models.dhcplease import DHCPLease |
599 | +from maasserver.models.macaddress import update_mac_cluster_interfaces |
600 | from maasserver.models.nodegroup import NodeGroup |
601 | from maasserver.utils.async import transactional |
602 | from provisioningserver.pserv_services.lease_upload_service import ( |
The diff is much bigger than really needed because I had to relocate update_ mac_cluster_ interfaces( ) to somewhere better so to avoid circular imports.