Merge lp:~jtv/maas/set-nameserver-in-interfaces 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: 3198
Proposed branch: lp:~jtv/maas/set-nameserver-in-interfaces
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 358 lines (+101/-28)
11 files modified
src/maasserver/clusterrpc/osystems.py (+2/-0)
src/maasserver/networking_preseed.py (+1/-0)
src/maasserver/tests/test_networking_preseed.py (+3/-0)
src/provisioningserver/drivers/osystem/__init__.py (+3/-2)
src/provisioningserver/drivers/osystem/debian_networking.py (+30/-7)
src/provisioningserver/drivers/osystem/tests/test_debian_networking.py (+48/-13)
src/provisioningserver/drivers/osystem/ubuntu.py (+3/-2)
src/provisioningserver/rpc/clusterservice.py (+3/-1)
src/provisioningserver/rpc/osystems.py (+4/-2)
src/provisioningserver/rpc/tests/test_clusterservice.py (+3/-1)
src/provisioningserver/rpc/tests/test_osystems.py (+1/-0)
To merge this branch: bzr merge lp:~jtv/maas/set-nameserver-in-interfaces
Reviewer Review Type Date Requested Status
Graham Binns (community) Approve
Review via email: mp+237231@code.launchpad.net

Commit message

Add IPv6 DNS server address to network configuration on IPv6-enabled nodes.

Uses a helper that had already been written (and tested): list_dns_servers. Also, “breaks open” the extract_ip helper because part of its work was also useful in this new case.

Description of the change

Most of the diff will probably be boilerplate for propagating the new parameter.

Jeroen

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/clusterrpc/osystems.py'
2--- src/maasserver/clusterrpc/osystems.py 2014-10-02 10:05:43 +0000
3+++ src/maasserver/clusterrpc/osystems.py 2014-10-06 10:17:13 +0000
4@@ -153,6 +153,7 @@
5 'aa:bb:cc:dd:ee:ff': ['10.9.1.1'],
6 '00:11:22:33:44:55': ['192.168.32.254'],
7 },
8+ 'nameservers': ['10.9.1.1'],
9 }
10
11 :param node: A `Node`.
12@@ -166,6 +167,7 @@
13 `gateways_mapping` maps to a dict which maps MAC addresses to lists of
14 gateway IP addresses (at most one IPv4 and one IPv6) to be used by the
15 corresponding network interfaces.
16+ 'nameservers' maps to a list of DNS servers for the node to use.
17 :return: A list of preseed dicts.
18 """
19 client = getClientFor(node.nodegroup.uuid)
20
21=== modified file 'src/maasserver/networking_preseed.py'
22--- src/maasserver/networking_preseed.py 2014-10-02 13:17:15 +0000
23+++ src/maasserver/networking_preseed.py 2014-10-06 10:17:13 +0000
24@@ -334,6 +334,7 @@
25 'auto_interfaces': find_macs_for_automatic_interfaces(node),
26 'ips_mapping': map_static_ips(node),
27 'gateways_mapping': map_gateways(node),
28+ 'nameservers': list_dns_servers(node),
29 }
30 preseed = compose_curtin_network_preseed(node, config)
31 return [json.dumps(item) for item in preseed]
32
33=== modified file 'src/maasserver/tests/test_networking_preseed.py'
34--- src/maasserver/tests/test_networking_preseed.py 2014-10-02 13:17:15 +0000
35+++ src/maasserver/tests/test_networking_preseed.py 2014-10-06 10:17:13 +0000
36@@ -769,6 +769,8 @@
37 node.nodegroup.accept()
38 network = factory.make_ipv4_network(slash=24)
39 router = factory.pick_ip_in_network(network)
40+ dns = factory.pick_ip_in_network(network)
41+ patch_dns_servers(self, dns)
42 static_low = unicode(IPAddress(network.first + 1))
43 static_high = unicode(IPAddress(network.first + 2))
44 dyn_low = unicode(IPAddress(network.first + 3))
45@@ -790,6 +792,7 @@
46 'auto_interfaces': [mac.mac_address],
47 'ips_mapping': {},
48 'gateways_mapping': {mac.mac_address: [router]},
49+ 'nameservers': [dns],
50 }
51 self.assertThat(fake, MockCalledOnceWith(node, expected_config))
52
53
54=== modified file 'src/provisioningserver/drivers/osystem/__init__.py'
55--- src/provisioningserver/drivers/osystem/__init__.py 2014-10-01 03:58:43 +0000
56+++ src/provisioningserver/drivers/osystem/__init__.py 2014-10-06 10:17:13 +0000
57@@ -194,9 +194,9 @@
58 """
59 raise NotImplementedError()
60
61- def compose_curtin_network_preseed(interfaces, auto_interfaces,
62+ def compose_curtin_network_preseed(self, interfaces, auto_interfaces,
63 ips_mapping, gateways_mapping,
64- disable_ipv4=False):
65+ disable_ipv4=False, nameservers=None):
66 """Compose a Curtin preseed to configure a node's networking.
67
68 :param interfaces: A list of tuples, each a pair of an interface name
69@@ -217,6 +217,7 @@
70 corresponding to each MAC to use the given default gateways.
71 :param disable_ipv4: Should this node be installed without IPv4
72 networking?
73+ :param nameservers: Optional list of DNS servers.
74 :return: A list of dicts that can be JSON-encoded and submitted to
75 Curtin as preseeds, perhaps in combination with other preseeds.
76 """
77
78=== modified file 'src/provisioningserver/drivers/osystem/debian_networking.py'
79--- src/provisioningserver/drivers/osystem/debian_networking.py 2014-10-01 04:10:51 +0000
80+++ src/provisioningserver/drivers/osystem/debian_networking.py 2014-10-06 10:17:13 +0000
81@@ -19,6 +19,20 @@
82 from netaddr import IPAddress
83
84
85+def extract_ip_from_sequence(ips, ip_version):
86+ """Return the first address for the given IP version from `ips`.
87+
88+ :param ips: A sequence of IP address strings.
89+ :param ip_version: Either 4 or 6 (for IPv4 or IPv6 respectively). Only an
90+ address for this version will be returned.
91+ :return: A matching IP address, or `None`.
92+ """
93+ for ip in ips:
94+ if IPAddress(ip).version == ip_version:
95+ return ip
96+ return None
97+
98+
99 def extract_ip(mapping, mac, ip_version):
100 """Extract IP address for `mac` and given IP version from `mapping`.
101
102@@ -29,10 +43,7 @@
103 address for this version will be returned.
104 :return: A matching IP address, or `None`.
105 """
106- for ip in mapping.get(mac, []):
107- if IPAddress(ip).version == ip_version:
108- return ip
109- return None
110+ return extract_ip_from_sequence(mapping.get(mac, []), ip_version)
111
112
113 def compose_ipv4_stanza(interface):
114@@ -40,7 +51,7 @@
115 return "iface %s inet dhcp" % interface
116
117
118-def compose_ipv6_stanza(interface, ip, gateway=None):
119+def compose_ipv6_stanza(interface, ip, gateway=None, nameserver=None):
120 """Return a Debian `/etc/network/interfaces` stanza for IPv6.
121
122 The stanza will configure a static address.
123@@ -52,6 +63,9 @@
124 ]
125 if gateway is not None:
126 lines.append('\tgateway %s' % gateway)
127+ if nameserver is not None:
128+ # Actually this keyword accepts up to 2 nameservers.
129+ lines.append('\tdns-nameservers %s' % nameserver)
130 return '\n'.join(lines)
131
132
133@@ -69,7 +83,8 @@
134
135
136 def compose_network_interfaces(interfaces, auto_interfaces, ips_mapping,
137- gateways_mapping, disable_ipv4=False):
138+ gateways_mapping, disable_ipv4=False,
139+ nameservers=None):
140 """Return contents for a node's `/etc/network/interfaces` file.
141
142 :param interfaces: A list of interface/MAC pairs for the node.
143@@ -80,7 +95,13 @@
144 :param gateways_mapping: A `defaultdict` mapping MAC addresses to
145 containers of the corresponding network interfaces' default gateways.
146 :param disable_ipv4: Should this node be installed without IPv4 networking?
147+ :param nameservers: Optional list of DNS servers.
148 """
149+ if nameservers is None:
150+ ipv6_nameserver = None
151+ else:
152+ ipv6_nameserver = extract_ip_from_sequence(nameservers, 6)
153+
154 # Should we disable IPv4 on this node? For safety's sake, we won't do this
155 # if the node has no static IPv6 addresses. Otherwise it might become
156 # accidentally unaddressable: it may have IPv6 addresses, but apart from
157@@ -99,5 +120,7 @@
158 if static_ipv6 is not None:
159 gateway = extract_ip(gateways_mapping, mac, 6)
160 stanzas.append(
161- compose_ipv6_stanza(interface, static_ipv6, gateway))
162+ compose_ipv6_stanza(
163+ interface, static_ipv6, gateway=gateway,
164+ nameserver=ipv6_nameserver))
165 return '%s\n' % '\n\n'.join(stanzas)
166
167=== modified file 'src/provisioningserver/drivers/osystem/tests/test_debian_networking.py'
168--- src/provisioningserver/drivers/osystem/tests/test_debian_networking.py 2014-10-01 04:09:58 +0000
169+++ src/provisioningserver/drivers/osystem/tests/test_debian_networking.py 2014-10-06 10:17:13 +0000
170@@ -65,7 +65,21 @@
171 """) % (interface, ip, gateway)
172 self.assertEqual(
173 expected.strip(),
174- compose_ipv6_stanza(interface, ip, gateway).strip())
175+ compose_ipv6_stanza(interface, ip, gateway=gateway).strip())
176+
177+ def test__adds_nameserver_if_given(self):
178+ ip = factory.make_ipv6_address()
179+ interface = factory.make_name('eth')
180+ nameserver = factory.make_ipv6_address()
181+ expected = dedent("""\
182+ iface %s inet6 static
183+ \tnetmask 64
184+ \taddress %s
185+ \tdns-nameservers %s
186+ """) % (interface, ip, nameserver)
187+ self.assertEqual(
188+ expected.strip(),
189+ compose_ipv6_stanza(interface, ip, nameserver=nameserver).strip())
190
191
192 class TestHasStaticIPv6Address(MAASTestCase):
193@@ -159,21 +173,42 @@
194 self.make_listing(interface, mac), [],
195 self.make_mapping(mac, {ipv6}), {}, disable_ipv4=disable_ipv4))
196
197- def test__passes_ip_and_gateway_when_creating_IPv6_stanza(self):
198+ def test__passes_ip_gateway_and_nameserver_when_creating_IPv6_stanza(self):
199 interface = factory.make_name('eth')
200 mac = factory.make_mac_address()
201 ipv6 = factory.make_ipv6_address()
202 gateway = factory.make_ipv6_address()
203- fake = self.patch_autospec(debian_networking, 'compose_ipv6_stanza')
204- fake.return_value = factory.make_name('stanza')
205-
206- compose_network_interfaces(
207- self.make_listing(interface, mac), [],
208- self.make_mapping(mac, {ipv6}), self.make_mapping(mac, {gateway}))
209-
210- self.assertThat(fake, MockCalledOnceWith(interface, ipv6, gateway))
211-
212- def test__omits_gateway_if_not_set(self):
213+ nameserver = factory.make_ipv6_address()
214+ fake = self.patch_autospec(debian_networking, 'compose_ipv6_stanza')
215+ fake.return_value = factory.make_name('stanza')
216+
217+ compose_network_interfaces(
218+ self.make_listing(interface, mac), [],
219+ self.make_mapping(mac, {ipv6}), self.make_mapping(mac, {gateway}),
220+ nameservers=[nameserver])
221+
222+ self.assertThat(
223+ fake, MockCalledOnceWith(
224+ interface, ipv6, gateway=gateway, nameserver=nameserver))
225+
226+ def test__ignores_IPv4_nameserver_when_creating_IPv6_stanza(self):
227+ interface = factory.make_name('eth')
228+ mac = factory.make_mac_address()
229+ ipv6 = factory.make_ipv6_address()
230+ nameserver = factory.make_ipv4_address()
231+ fake = self.patch_autospec(debian_networking, 'compose_ipv6_stanza')
232+ fake.return_value = factory.make_name('stanza')
233+
234+ compose_network_interfaces(
235+ self.make_listing(interface, mac), [],
236+ self.make_mapping(mac, {ipv6}), gateways_mapping={},
237+ nameservers=[nameserver])
238+
239+ self.assertThat(
240+ fake, MockCalledOnceWith(
241+ interface, ANY, gateway=ANY, nameserver=None))
242+
243+ def test__omits_gateway_and_nameserver_if_not_set(self):
244 interface = factory.make_name('eth')
245 mac = factory.make_mac_address()
246 fake = self.patch_autospec(debian_networking, 'compose_ipv6_stanza')
247@@ -184,7 +219,7 @@
248
249 self.assertThat(
250 fake,
251- MockCalledOnceWith(interface, ANY, None))
252+ MockCalledOnceWith(interface, ANY, gateway=None, nameserver=None))
253
254 def test__writes_auto_lines_for_interfaces_in_auto_interfaces(self):
255 interface = factory.make_name('eth')
256
257=== modified file 'src/provisioningserver/drivers/osystem/ubuntu.py'
258--- src/provisioningserver/drivers/osystem/ubuntu.py 2014-10-01 03:58:43 +0000
259+++ src/provisioningserver/drivers/osystem/ubuntu.py 2014-10-06 10:17:13 +0000
260@@ -92,7 +92,7 @@
261
262 def compose_curtin_network_preseed(self, interfaces, auto_interfaces,
263 ips_mapping, gateways_mapping,
264- disable_ipv4=False):
265+ disable_ipv4=False, nameservers=None):
266 """As defined in `OperatingSystem`: generate networking Curtin preseed.
267
268 Supports:
269@@ -103,7 +103,8 @@
270 """
271 interfaces_file = compose_network_interfaces(
272 interfaces, auto_interfaces, ips_mapping=ips_mapping,
273- gateways_mapping=gateways_mapping, disable_ipv4=disable_ipv4)
274+ gateways_mapping=gateways_mapping, disable_ipv4=disable_ipv4,
275+ nameservers=nameservers)
276 udev_rules = compose_network_interfaces_udev_rules(interfaces)
277 write_files = {
278 'write_files': {
279
280=== modified file 'src/provisioningserver/rpc/clusterservice.py'
281--- src/provisioningserver/rpc/clusterservice.py 2014-10-03 13:04:16 +0000
282+++ src/provisioningserver/rpc/clusterservice.py 2014-10-06 10:17:13 +0000
283@@ -225,10 +225,12 @@
284 auto_interfaces = config.get('auto_interfaces', [])
285 ips_mapping = config.get('ips_mapping', {})
286 gateways_mapping = config.get('gateways_mapping', {})
287+ nameservers = config.get('nameservers', [])
288 return {
289 'data': compose_curtin_network_preseed(
290 osystem, interfaces, auto_interfaces, ips_mapping=ips_mapping,
291- gateways_mapping=gateways_mapping, disable_ipv4=disable_ipv4),
292+ gateways_mapping=gateways_mapping, disable_ipv4=disable_ipv4,
293+ nameservers=nameservers),
294 }
295
296 @log_call(level=logging.DEBUG)
297
298=== modified file 'src/provisioningserver/rpc/osystems.py'
299--- src/provisioningserver/rpc/osystems.py 2014-10-01 03:58:43 +0000
300+++ src/provisioningserver/rpc/osystems.py 2014-10-06 10:17:13 +0000
301@@ -119,7 +119,7 @@
302
303 def compose_curtin_network_preseed(os_name, interfaces, auto_interfaces,
304 ips_mapping, gateways_mapping,
305- disable_ipv4):
306+ disable_ipv4, nameservers):
307 """Compose Curtin network preseed for a node.
308
309 :param os_name: Identifying name of the operating system for which a
310@@ -132,6 +132,7 @@
311 :param gateways_mapping: A `defaultdict` mapping MAC addresses to
312 containers of the corresponding network interfaces' default gateways.
313 :param disable_ipv4: Should this node be installed without IPv4 networking?
314+ :param nameservers: List of DNS servers.
315 :return: Preseed data, as JSON.
316 """
317 try:
318@@ -141,4 +142,5 @@
319 else:
320 return osystem.compose_curtin_network_preseed(
321 interfaces, auto_interfaces, ips_mapping=ips_mapping,
322- gateways_mapping=gateways_mapping, disable_ipv4=disable_ipv4)
323+ gateways_mapping=gateways_mapping, disable_ipv4=disable_ipv4,
324+ nameservers=nameservers)
325
326=== modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py'
327--- src/provisioningserver/rpc/tests/test_clusterservice.py 2014-10-03 13:04:16 +0000
328+++ src/provisioningserver/rpc/tests/test_clusterservice.py 2014-10-06 10:17:13 +0000
329@@ -1121,6 +1121,7 @@
330 'auto_interfaces': [mac],
331 'ips_mapping': {mac: [factory.make_ipv4_address()]},
332 'gateways_mapping': {mac: [factory.make_ipv4_address()]},
333+ 'nameservers': [],
334 },
335 'disable_ipv4': factory.pick_bool(),
336 }
337@@ -1134,7 +1135,8 @@
338 @inlineCallbacks
339 def test__calls_compose_curtin_network_preseed(self):
340 preseed = [factory.make_name('preseed')]
341- fake = self.patch(clusterservice, 'compose_curtin_network_preseed')
342+ fake = self.patch_autospec(
343+ clusterservice, 'compose_curtin_network_preseed')
344 fake.return_value = preseed
345 args = self.make_args()
346
347
348=== modified file 'src/provisioningserver/rpc/tests/test_osystems.py'
349--- src/provisioningserver/rpc/tests/test_osystems.py 2014-10-01 03:58:43 +0000
350+++ src/provisioningserver/rpc/tests/test_osystems.py 2014-10-06 10:17:13 +0000
351@@ -221,6 +221,7 @@
352 ],
353 },
354 'disable_ipv4': factory.pick_bool(),
355+ 'nameservers': [factory.make_ipv6_address()],
356 }
357
358 def test__forwards_to_OS_implementation(self):