Merge lp:~jtv/maas/clean-up-networking_preseed 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: 3154
Proposed branch: lp:~jtv/maas/clean-up-networking_preseed
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 275 lines (+63/-43)
2 files modified
src/maasserver/networking_preseed.py (+21/-17)
src/maasserver/tests/test_networking_preseed.py (+42/-26)
To merge this branch: bzr merge lp:~jtv/maas/clean-up-networking_preseed
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+236657@code.launchpad.net

Commit message

Simplify much-used data structure in networking_preseed: instead of defaultdict-of-sets-of-IPAddress, just work with dict-of-sets-of-unicode, with the unicode strings containing normalised IP addresses.

Description of the change

This will simplify the job of generating a node's networking configuration data server-side. These data structures need to be sent over RPC, so simple is good.

Jeroen

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good. One little mini-rant about setdefault, but that's all.

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

Thanks. I do agree in the general case. Here, it was the intermediate JSON step for RPC that made me do it. I wanted to avoid as much as I could any subtle differences between the same data structure on the region side and the cluster side. (And yes I do feel slightly awkward about using set() for that reason, but that seemed more effort to remove.)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/networking_preseed.py'
2--- src/maasserver/networking_preseed.py 2014-09-30 04:57:45 +0000
3+++ src/maasserver/networking_preseed.py 2014-10-01 06:35:17 +0000
4@@ -28,8 +28,6 @@
5 'generate_networking_config',
6 ]
7
8-from collections import defaultdict
9-
10 from lxml import etree
11 from maasserver.dns.zonegenerator import get_dns_server_address
12 from maasserver.exceptions import UnresolvableHost
13@@ -38,6 +36,7 @@
14 from netaddr import (
15 IPAddress,
16 IPNetwork,
17+ valid_ipv4,
18 )
19
20
21@@ -68,6 +67,14 @@
22 return mac.strip().lower()
23
24
25+def normalise_ip(ip):
26+ """Return an IP address' normalised representation.
27+
28+ IPv6 addresses in particular can have many different spellings.
29+ """
30+ return unicode(IPAddress(ip.strip()))
31+
32+
33 def extract_network_interfaces(node):
34 """Extract network interfaces from node's `lshw` output.
35
36@@ -152,7 +159,7 @@
37 if cluster_interface.router_ip in ('', None):
38 # No routes available.
39 return []
40- elif IPAddress(cluster_interface.ip).version == 4:
41+ elif valid_ipv4(cluster_interface.ip):
42 return [
43 {
44 'network': '0.0.0.0',
45@@ -252,28 +259,25 @@
46 def add_ip_to_mapping(mapping, macaddress, ip):
47 """Add IP address to a `defaultdict` keyed by MAC string.
48
49- :param mapping: A `defaultdict` (defaulting to the empty set) mapping
50- normalised MAC address strings to sets of `IPAddress`.
51+ :param mapping: A `dict` mapping normalised MAC address strings to sets of
52+ normalised IP address strings.
53 :param macaddress: A `MACAddress`.
54 :param ip: An IP address string. If it is empty or `None`, it will not
55 be added to the mapping.
56 """
57- if ip in (None, ''):
58- return
59- assert isinstance(ip, (unicode, bytes, IPAddress))
60- mac = extract_mac_string(macaddress)
61- ip = IPAddress(ip)
62- mapping[mac].add(ip)
63+ if ip not in (None, ''):
64+ mac = extract_mac_string(macaddress)
65+ mapping.setdefault(mac, set()).add(normalise_ip(ip))
66
67
68 def map_static_ips(node):
69 """Return a `defaultdict` mapping node's MAC addresses to their static IPs.
70
71 :param node: A `Node`.
72- :return: A `defaultdict` (defaulting to the empty set) mapping normalised
73- MAC address strings to sets of `IPAddress`.
74+ :return: A dict mapping normalised MAC address strings to sets of
75+ normalised IP address strings.
76 """
77- mapping = defaultdict(set)
78+ mapping = {}
79 for sip in StaticIPAddress.objects.filter(macaddress__node=node):
80 for mac in sip.macaddress_set.all():
81 add_ip_to_mapping(mapping, mac, sip.ip)
82@@ -284,10 +288,10 @@
83 """Return a `defaultdict` mapping node's MAC addresses to their gateways.
84
85 :param node: A `Node`.
86- :return: A `defaultdict` (defaulting to the empty set) mapping normalised
87- MAC address strings to sets of `IPAddress`.
88+ :return: A dict mapping normalised MAC address strings to sets of
89+ normalised IP address strings.
90 """
91- mapping = defaultdict(set)
92+ mapping = {}
93 for mac in node.macaddress_set.all():
94 for cluster_interface in mac.get_cluster_interfaces():
95 if cluster_interface.manages_static_range():
96
97=== modified file 'src/maasserver/tests/test_networking_preseed.py'
98--- src/maasserver/tests/test_networking_preseed.py 2014-09-30 04:57:45 +0000
99+++ src/maasserver/tests/test_networking_preseed.py 2014-10-01 06:35:17 +0000
100@@ -15,7 +15,6 @@
101 __all__ = [
102 ]
103
104-from collections import defaultdict
105 from random import randint
106
107 from maasserver import networking_preseed
108@@ -37,12 +36,12 @@
109 list_dns_servers,
110 map_gateways,
111 map_static_ips,
112+ normalise_ip,
113 normalise_mac,
114 )
115 from maasserver.testing.factory import factory
116 from maasserver.testing.testcase import MAASServerTestCase
117 from maastesting.matchers import MockCalledOnceWith
118-from netaddr import IPAddress
119 from testtools.matchers import HasLength
120
121
122@@ -222,6 +221,30 @@
123 normalise_mac(normalise_mac(mac)))
124
125
126+class TestNormaliseIP(MAASServerTestCase):
127+
128+ def test__normalises_case(self):
129+ ip = factory.make_ipv6_address()
130+ self.assertEqual(
131+ normalise_ip(ip.upper()),
132+ normalise_ip(ip.lower()))
133+
134+ def test__strips_whitespace(self):
135+ ip = factory.make_ipv4_address()
136+ self.assertEqual(ip, normalise_ip(' %s ' % ip))
137+
138+ def test__normalises_zeroes(self):
139+ self.assertEqual('::1', normalise_ip('0000:000:00:0::1'))
140+
141+ def test__accepts_bytes(self):
142+ ip = factory.make_ipv6_address()
143+ self.assertEqual(normalise_ip(ip), normalise_ip(ip.encode('ascii')))
144+
145+ def test__is_idempotent(self):
146+ ip = factory.make_ipv6_address()
147+ self.assertEqual(normalise_ip(ip), normalise_ip(normalise_ip(ip)))
148+
149+
150 class TestGenerateEthernetLinkEntry(MAASServerTestCase):
151
152 def test__generates_dict(self):
153@@ -517,38 +540,31 @@
154
155 class TestAddIPToMapping(MAASServerTestCase):
156
157- def make_mapping(self):
158- return defaultdict(set)
159-
160 def test__adds_to_empty_entry(self):
161- mapping = self.make_mapping()
162 mac = factory.make_MACAddress()
163 ip = factory.make_ipv4_address()
164+ mapping = {}
165 add_ip_to_mapping(mapping, mac, ip)
166- self.assertEqual(
167- {mac.mac_address: {IPAddress(ip)}},
168- mapping)
169+ self.assertEqual({mac.mac_address: {ip}}, mapping)
170
171 def test__adds_to_nonempty_entry(self):
172- mapping = self.make_mapping()
173+ mapping = {}
174 mac = factory.make_MACAddress()
175 ip1 = factory.make_ipv4_address()
176 add_ip_to_mapping(mapping, mac, ip1)
177 ip2 = factory.make_ipv4_address()
178 add_ip_to_mapping(mapping, mac, ip2)
179- self.assertEqual(
180- {mac.mac_address: {IPAddress(ip1), IPAddress(ip2)}},
181- mapping)
182+ self.assertEqual({mac.mac_address: {ip1, ip2}}, mapping)
183
184 def test__does_not_add_None(self):
185- mapping = self.make_mapping()
186 mac = factory.make_MACAddress()
187+ mapping = {}
188 add_ip_to_mapping(mapping, mac, None)
189 self.assertEqual({}, mapping)
190
191 def test__does_not_add_empty_string(self):
192- mapping = self.make_mapping()
193 mac = factory.make_MACAddress()
194+ mapping = {}
195 add_ip_to_mapping(mapping, mac, '')
196 self.assertEqual({}, mapping)
197
198@@ -564,7 +580,7 @@
199 ip = factory.make_ipv4_address()
200 factory.make_StaticIPAddress(ip=ip, mac=mac)
201 self.assertEqual(
202- {mac.mac_address: {IPAddress(ip)}},
203+ {mac.mac_address: {ip}},
204 map_static_ips(node))
205
206 def test__finds_IPv6_address(self):
207@@ -573,7 +589,7 @@
208 ip = factory.make_ipv6_address()
209 factory.make_StaticIPAddress(ip=ip, mac=mac)
210 self.assertEqual(
211- {mac.mac_address: {IPAddress(ip)}},
212+ {mac.mac_address: {ip}},
213 map_static_ips(node))
214
215 def test__finds_addresses_on_multiple_MACs(self):
216@@ -586,8 +602,8 @@
217 factory.make_StaticIPAddress(ip=ip2, mac=mac2)
218 self.assertEqual(
219 {
220- mac1.mac_address: {IPAddress(ip1)},
221- mac2.mac_address: {IPAddress(ip2)},
222+ mac1.mac_address: {ip1},
223+ mac2.mac_address: {ip2},
224 },
225 map_static_ips(node))
226
227@@ -599,7 +615,7 @@
228 factory.make_StaticIPAddress(ip=ipv4, mac=mac)
229 factory.make_StaticIPAddress(ip=ipv6, mac=mac)
230 self.assertEqual(
231- {mac.mac_address: {IPAddress(ipv4), IPAddress(ipv6)}},
232+ {mac.mac_address: {ipv4, ipv6}},
233 map_static_ips(node))
234
235
236@@ -620,7 +636,7 @@
237 node=node, cluster_interface=cluster_interface)
238
239 self.assertEqual(
240- {mac.mac_address: {IPAddress(gateway)}},
241+ {mac.mac_address: {gateway}},
242 map_gateways(node))
243
244 def test__finds_IPv6_gateway(self):
245@@ -640,7 +656,7 @@
246 node=node, cluster_interface=ipv4_interface)
247
248 self.assertEqual(
249- {mac.mac_address: {IPAddress(gateway)}},
250+ {mac.mac_address: {gateway}},
251 map_gateways(node))
252
253 def test__finds_gateways_on_multiple_MACs(self):
254@@ -663,8 +679,8 @@
255
256 self.assertEqual(
257 {
258- mac1.mac_address: {IPAddress(gateway1)},
259- mac2.mac_address: {IPAddress(gateway2)},
260+ mac1.mac_address: {gateway1},
261+ mac2.mac_address: {gateway2},
262 },
263 map_gateways(node))
264
265@@ -690,8 +706,8 @@
266 self.assertEqual(
267 {
268 mac.mac_address: {
269- IPAddress(ipv4_gateway),
270- IPAddress(ipv6_gateway),
271+ ipv4_gateway,
272+ ipv6_gateway,
273 },
274 },
275 map_gateways(node))