Merge lp:~julian-edwards/maas/always-send-leases 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: 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
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.

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

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.

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

Just the changes in the imports are enough to suggest that you moved update_mac_cluster_interfaces() to a better place. Aren't there any tests to move though?

It seems heavy-handed to run all of the cluster's leases through update_mac_cluster_interfaces just because you're adding one MAC to one node... Would it not be enough to pass just the leases for that one MAC?

review: Approve
Revision history for this message
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_mac_cluster_interfaces() to a better place. Aren't there any tests
> to move though?

Ah good point, I forgot those, thanks.

> It seems heavy-handed to run all of the cluster's leases through
> update_mac_cluster_interfaces just because you're adding one MAC to one
> 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!

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (35.4 KiB)

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://security.ubuntu.com trusty-security InRelease
Hit http://security.ubuntu.com trusty-security Release.gpg
Hit http://security.ubuntu.com trusty-security Release
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Hit http://nova.clouds.archive.ubuntu.com trusty-updates Release
Hit http://security.ubuntu.com trusty-security/main Sources
Hit http://security.ubuntu.com trusty-security/universe Sources
Hit http://security.ubuntu.com trusty-security/main amd64 Packages
Hit http://security.ubuntu.com trusty-security/universe amd64 Packages
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make pep8 postgresql pyflakes python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lockfile python-lxml python-mimeparse python-mock python-netaddr python-netifaces python-nose python-oauth python-oops python-oops-amqp python-oops-datedir-repo python-oops-twisted python-oops-wsgi python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-seamicroclient python-simplejson python-simplestreams python-sphi...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 (