Merge lp:~mpontillo/maas/subnet-dns-override-maas-dns-for-default-gw--bug-1576116--2.0 into lp:maas/2.0

Proposed by Mike Pontillo on 2016-07-15
Status: Merged
Approved by: Mike Pontillo on 2016-07-15
Approved revision: 5163
Merged at revision: 5162
Proposed branch: lp:~mpontillo/maas/subnet-dns-override-maas-dns-for-default-gw--bug-1576116--2.0
Merge into: lp:maas/2.0
Diff against target: 366 lines (+225/-17)
5 files modified
docs/changelog.rst (+5/-0)
src/maasserver/models/node.py (+60/-5)
src/maasserver/models/tests/test_node.py (+155/-0)
src/maasserver/preseed_network.py (+3/-6)
src/maasserver/tests/test_preseed_network.py (+2/-6)
To merge this branch: bzr merge lp:~mpontillo/maas/subnet-dns-override-maas-dns-for-default-gw--bug-1576116--2.0
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve on 2016-07-15
Review via email: mp+300148@code.launchpad.net

Commit message

Merge revision 5178 from trunk.

When deploying a node, override the MAAS DNS server with the DNS servers specified on the subnet for the default gateway, when specifying DNS servers for the loopback interface.

To post a comment you must log in.
Mike Pontillo (mpontillo) wrote :

Self-approved backport.

review: Approve
MAAS Lander (maas-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/changelog.rst'
2--- docs/changelog.rst 2016-07-14 23:49:58 +0000
3+++ docs/changelog.rst 2016-07-15 02:30:37 +0000
4@@ -15,6 +15,8 @@
5
6 LP: #1603147 Commissioning dropdown is grey and checkmarks are missing.
7
8+LP: #1576116 MAAS adds regions controller as DNS resolver
9+
10
11 2.0.0 (rc2)
12 ===========
13@@ -61,6 +63,9 @@
14
15 LP: #1596046 [2.0] maas 2.0 pxeboot fails on PowerNV
16
17+LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP'
18+
19+LP: #1598937 [2.0 rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db'
20
21 2.0.0 (beta8)
22 =============
23
24=== modified file 'src/maasserver/models/node.py'
25--- src/maasserver/models/node.py 2016-06-17 21:26:46 +0000
26+++ src/maasserver/models/node.py 2016-07-15 02:30:37 +0000
27@@ -209,8 +209,23 @@
28
29 # Return type from `get_effective_power_info`.
30 PowerInfo = namedtuple("PowerInfo", (
31- "can_be_started", "can_be_stopped", "can_be_queried", "power_type",
32- "power_parameters"))
33+ "can_be_started",
34+ "can_be_stopped",
35+ "can_be_queried",
36+ "power_type",
37+ "power_parameters",
38+))
39+
40+DefaultGateways = namedtuple("DefaultGateways", (
41+ "ipv4",
42+ "ipv6",
43+))
44+
45+GatewayDefinition = namedtuple("GatewayDefinition", (
46+ "interface_id",
47+ "subnet_id",
48+ "gateway_ip",
49+))
50
51 # The sequence from which the decimal form of node system IDs should be
52 # pulled. At the time of writing this should match the definition in migration
53@@ -2568,7 +2583,7 @@
54 interface.id
55 """, (self.id,))
56 return [
57- (found[0], found[1], found[2])
58+ GatewayDefinition._make((found[0], found[1], found[2]))
59 for found in cursor.fetchall()
60 ]
61
62@@ -2620,7 +2635,7 @@
63
64 # Early out if we already have both gateways.
65 if gateway_ipv4 and gateway_ipv6:
66- return (gateway_ipv4, gateway_ipv6)
67+ return DefaultGateways._make((gateway_ipv4, gateway_ipv6))
68
69 # Get the best guesses for the missing IP families.
70 found_gateways = self.get_best_guess_for_default_gateways()
71@@ -2630,7 +2645,47 @@
72 if not gateway_ipv6:
73 gateway_ipv6 = self._get_gateway_tuple_by_family(
74 found_gateways, IPADDRESS_FAMILY.IPv6)
75- return (gateway_ipv4, gateway_ipv6)
76+ return DefaultGateways._make((gateway_ipv4, gateway_ipv6))
77+
78+ def get_default_dns_servers(self):
79+ """Return the default DNS servers for this node."""
80+ # Circular imports.
81+ from maasserver.dns.zonegenerator import get_dns_server_address
82+
83+ gateways = self.get_default_gateways()
84+
85+ # Try first to use DNS servers from default gateway subnets.
86+ if gateways.ipv4 is not None:
87+ subnet = Subnet.objects.get(id=gateways.ipv4.subnet_id)
88+ if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
89+ # An IPv4 subnet is hosting the default gateway and has DNS
90+ # servers defined. IPv4 DNS servers take first-priority.
91+ return subnet.dns_servers
92+ if gateways.ipv6 is not None:
93+ subnet = Subnet.objects.get(id=gateways.ipv6.subnet_id)
94+ if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
95+ # An IPv6 subnet is hosting the default gateway and has DNS
96+ # servers defined. IPv6 DNS servers take second-priority.
97+ return subnet.dns_servers
98+
99+ # No default gateway subnet has specific DNS servers defined, so
100+ # use MAAS for the default DNS server.
101+ if gateways.ipv4 is None and gateways.ipv6 is None:
102+ # If there are no default gateways, the default is the MAAS
103+ # region IP address.
104+ maas_dns_server = get_dns_server_address(
105+ rack_controller=self.get_boot_rack_controller())
106+ else:
107+ # Choose an address consistent with the primary address-family
108+ # in use, as indicated by the presence (or not) of a gateway.
109+ # Note that this path is only taken if the MAAS URL is set to
110+ # a hostname, and the hostname resolves to both an IPv4 and an
111+ # IPv6 address.
112+ maas_dns_server = get_dns_server_address(
113+ rack_controller=self.get_boot_rack_controller(),
114+ ipv4=(gateways.ipv4 is not None),
115+ ipv6=(gateways.ipv6 is not None))
116+ return [maas_dns_server]
117
118 def get_boot_purpose(self):
119 """
120
121=== modified file 'src/maasserver/models/tests/test_node.py'
122--- src/maasserver/models/tests/test_node.py 2016-06-28 07:12:49 +0000
123+++ src/maasserver/models/tests/test_node.py 2016-07-15 02:30:37 +0000
124@@ -37,6 +37,7 @@
125 from maasserver.enum import (
126 FILESYSTEM_GROUP_TYPE,
127 FILESYSTEM_TYPE,
128+ INTERFACE_LINK_TYPE,
129 INTERFACE_TYPE,
130 IPADDRESS_TYPE,
131 NODE_PERMISSION,
132@@ -93,6 +94,7 @@
133 NODE_TRANSITIONS,
134 )
135 from maasserver.rpc.testing.fixtures import MockLiveRegionToClusterRPCFixture
136+import maasserver.server_address as server_address_module
137 from maasserver.storage_layouts import (
138 StorageLayoutError,
139 StorageLayoutMissingBootDiskError,
140@@ -134,6 +136,7 @@
141 commissioning,
142 disk_erasing,
143 )
144+from netaddr import IPAddress
145 from provisioningserver.events import (
146 EVENT_DETAILS,
147 EVENT_TYPES,
148@@ -4101,6 +4104,158 @@
149 ), node.get_default_gateways())
150
151
152+class TestGetDefaultDNSServers(MAASServerTestCase):
153+ """Tests for `Node.get_default_dns_servers`."""
154+
155+ def make_Node_with_RackController(
156+ self, ipv4=True, ipv6=True, ipv4_gateway=True, ipv6_gateway=True,
157+ ipv4_subnet_dns=None, ipv6_subnet_dns=None):
158+ ipv4_subnet_dns = [] if ipv4_subnet_dns is None else ipv4_subnet_dns
159+ ipv6_subnet_dns = [] if ipv6_subnet_dns is None else ipv6_subnet_dns
160+ rack_v4 = None
161+ rack_v6 = None
162+ fabric = factory.make_Fabric()
163+ vlan = fabric.get_default_vlan()
164+ if ipv4:
165+ gateway_ip = None if ipv4_gateway else ""
166+ v4_subnet = factory.make_Subnet(
167+ version=4, vlan=vlan, dns_servers=ipv4_subnet_dns,
168+ gateway_ip=gateway_ip)
169+ if ipv6:
170+ gateway_ip = None if ipv6_gateway else ""
171+ v6_subnet = factory.make_Subnet(
172+ version=6, vlan=vlan, dns_servers=ipv6_subnet_dns,
173+ gateway_ip=gateway_ip)
174+ rack = factory.make_RegionRackController()
175+ vlan.primary_rack = rack
176+ vlan.dhcp_on = True
177+ vlan.save()
178+ # In order to determine the correct IP address per-address-family,
179+ # a name lookup is performed on the hostname part of the URL.
180+ # We need to mock that so we can return whatever IP addresses it
181+ # resolves to.
182+ rack.url = "http://region:5240/MAAS/"
183+ if ipv4:
184+ rack_v4 = factory.pick_ip_in_Subnet(v4_subnet)
185+ if ipv6:
186+ rack_v6 = factory.pick_ip_in_Subnet(v6_subnet)
187+
188+ def get_address(hostname, ip_version=4):
189+ """Mock function to return the IP address of the rack based on the
190+ given address family.
191+ """
192+ if ip_version == 4:
193+ return {IPAddress(rack_v4)} if rack_v4 else set()
194+ elif ip_version == 6:
195+ return {IPAddress(rack_v6)} if rack_v6 else set()
196+
197+ resolve_hostname = self.patch(
198+ server_address_module, 'resolve_hostname')
199+ resolve_hostname.side_effect = get_address
200+ rack.interface_set.all().delete()
201+ rackif = factory.make_Interface(vlan=vlan, node=rack)
202+ if ipv4:
203+ rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v4_subnet, rack_v4)
204+ if ipv6:
205+ rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v6_subnet, rack_v6)
206+ rack.boot_interface = rackif
207+ rack.save()
208+ node = factory.make_Node(status=NODE_STATUS.READY, disable_ipv4=False)
209+ nodeif = factory.make_Interface(vlan=vlan, node=node)
210+ if ipv4:
211+ nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v4_subnet)
212+ if ipv6:
213+ nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v6_subnet)
214+ node.boot_interface = nodeif
215+ node.save()
216+ return rack_v4, rack_v6, node
217+
218+ def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway(self):
219+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
220+ ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False)
221+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
222+
223+ def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway_v4_dns(self):
224+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
225+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
226+ ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False,
227+ ipv4_subnet_dns=[ipv4_subnet_dns])
228+ self.assertThat(
229+ node.get_default_dns_servers(), Equals([rack_v4]))
230+
231+ def test__uses_rack_ipv6_if_ipv6_only_with_no_gateway_v6_dns(self):
232+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
233+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
234+ ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=False,
235+ ipv6_subnet_dns=[ipv6_subnet_dns])
236+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v6]))
237+
238+ def test__uses_rack_ipv4_if_dual_stack_with_no_gateway(self):
239+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
240+ ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=False)
241+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
242+
243+ def test__uses_rack_ipv4_if_dual_stack_with_ipv4_gateway(self):
244+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
245+ ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False)
246+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
247+
248+ def test__uses_subnet_ipv4_if_dual_stack_with_ipv4_gateway_with_dns(self):
249+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
250+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
251+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
252+ ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False,
253+ ipv4_subnet_dns=[ipv4_subnet_dns],
254+ ipv6_subnet_dns=[ipv6_subnet_dns])
255+ self.assertThat(
256+ node.get_default_dns_servers(), Equals([ipv4_subnet_dns]))
257+
258+ def test__uses_rack_ipv6_if_dual_stack_with_ipv6_gateway(self):
259+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
260+ ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True)
261+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v6]))
262+
263+ def test__uses_subnet_ipv6_if_dual_stack_with_ipv6_gateway(self):
264+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
265+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
266+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
267+ ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True,
268+ ipv4_subnet_dns=[ipv4_subnet_dns],
269+ ipv6_subnet_dns=[ipv6_subnet_dns])
270+ self.assertThat(
271+ node.get_default_dns_servers(), Equals([ipv6_subnet_dns]))
272+
273+ def test__uses_rack_ipv4_if_ipv4_with_ipv4_gateway(self):
274+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
275+ ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False)
276+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
277+
278+ def test__uses_subnet_ipv4_if_ipv4_stack_with_ipv4_gateway_and_dns(self):
279+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
280+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
281+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
282+ ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False,
283+ ipv4_subnet_dns=[ipv4_subnet_dns],
284+ ipv6_subnet_dns=[ipv6_subnet_dns])
285+ self.assertThat(
286+ node.get_default_dns_servers(), Equals([ipv4_subnet_dns]))
287+
288+ def test__uses_rack_ipv6_if_ipv6_with_ipv6_gateway(self):
289+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
290+ ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True)
291+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v6]))
292+
293+ def test__uses_subnet_ipv6_if_ipv6_with_ipv6_gateway_and_dns(self):
294+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
295+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
296+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
297+ ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True,
298+ ipv4_subnet_dns=[ipv4_subnet_dns],
299+ ipv6_subnet_dns=[ipv6_subnet_dns])
300+ self.assertThat(
301+ node.get_default_dns_servers(), Equals([ipv6_subnet_dns]))
302+
303+
304 class TestNode_Start(MAASServerTestCase):
305 """Tests for Node.start()."""
306
307
308=== modified file 'src/maasserver/preseed_network.py'
309--- src/maasserver/preseed_network.py 2016-03-28 13:54:47 +0000
310+++ src/maasserver/preseed_network.py 2016-07-15 02:30:37 +0000
311@@ -6,10 +6,7 @@
312 __all__ = [
313 ]
314
315-from maasserver.dns.zonegenerator import (
316- get_dns_search_paths,
317- get_dns_server_address,
318-)
319+from maasserver.dns.zonegenerator import get_dns_search_paths
320 from maasserver.enum import (
321 INTERFACE_TYPE,
322 IPADDRESS_FAMILY,
323@@ -24,6 +21,7 @@
324 def __init__(self, node):
325 self.node = node
326 self.gateways = node.get_default_gateways()
327+ self.dns_servers = node.get_default_dns_servers()
328 self.gateway_ipv4_set = False
329 self.gateway_ipv6_set = False
330 self.operations = {
331@@ -51,8 +49,7 @@
332
333 self.network_config.append({
334 "type": "nameserver",
335- "address": get_dns_server_address(
336- rack_controller=self.node.get_boot_rack_controller()),
337+ "address": self.dns_servers,
338 "search": sorted(get_dns_search_paths()),
339 })
340
341
342=== modified file 'src/maasserver/tests/test_preseed_network.py'
343--- src/maasserver/tests/test_preseed_network.py 2016-04-15 23:38:27 +0000
344+++ src/maasserver/tests/test_preseed_network.py 2016-07-15 02:30:37 +0000
345@@ -8,10 +8,7 @@
346 import random
347 from textwrap import dedent
348
349-from maasserver.dns.zonegenerator import (
350- get_dns_search_paths,
351- get_dns_server_address,
352-)
353+from maasserver.dns.zonegenerator import get_dns_search_paths
354 from maasserver.enum import (
355 INTERFACE_TYPE,
356 IPADDRESS_FAMILY,
357@@ -203,8 +200,7 @@
358
359 def collectDNSConfig(self, node):
360 config = "- type: nameserver\n address: %s\n search:\n" % (
361- get_dns_server_address(
362- rack_controller=node.get_boot_primary_rack_controller()))
363+ repr(node.get_default_dns_servers()))
364 dns_searches = sorted(get_dns_search_paths())
365 for dns_name in dns_searches:
366 config += " - %s\n" % dns_name

Subscribers

People subscribed via source and target branches

to all changes: