Merge lp:~mpontillo/maas/subnet-dhcp-dns-settings--bug-1600720--2.0 into lp:maas/2.0

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 5163
Proposed branch: lp:~mpontillo/maas/subnet-dhcp-dns-settings--bug-1600720--2.0
Merge into: lp:maas/2.0
Diff against target: 347 lines (+173/-63)
3 files modified
docs/changelog.rst (+3/-1)
src/maasserver/dhcp.py (+59/-32)
src/maasserver/tests/test_dhcp.py (+111/-30)
To merge this branch: bzr merge lp:~mpontillo/maas/subnet-dhcp-dns-settings--bug-1600720--2.0
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+300211@code.launchpad.net

Commit message

Merge revision 5169 from trunk.

Fix DHCP configuration to properly override subnet DNS with user-configured DNS. (This makes MAAS DHCP consistent with what curtin does.)

Drive-by fix to log an error when DHCP is enabled for a particular address family, but no subnet can be found. (Previous behavior was to silently fail to write the configuration; now if IPv6 is misconfigured, it will not prevent the IPv4 configuration from being written, and vice versa.)

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Self-reviewed backport.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'docs/changelog.rst'
--- docs/changelog.rst 2016-07-15 02:22:48 +0000
+++ docs/changelog.rst 2016-07-15 15:53:19 +0000
@@ -15,7 +15,9 @@
1515
16LP: #1603147 Commissioning dropdown is grey and checkmarks are missing.16LP: #1603147 Commissioning dropdown is grey and checkmarks are missing.
1717
18LP: #1576116 MAAS adds regions controller as DNS resolver18LP: #1576116 [2.0rc1] MAAS does not respect default subnet's DNS server when choosing default DNS
19
20LP: #1600720 [2.0rc1] MAAS doesn't honor DNS settings for a subnet for DHCP
1921
2022
212.0.0 (rc2)232.0.0 (rc2)
2224
=== modified file 'src/maasserver/dhcp.py'
--- src/maasserver/dhcp.py 2016-04-13 13:18:40 +0000
+++ src/maasserver/dhcp.py 2016-07-15 15:53:19 +0000
@@ -316,10 +316,17 @@
316316
317317
318def make_subnet_config(318def make_subnet_config(
319 rack_controller, subnet, dns_servers, ntp_server, default_domain,319 rack_controller, subnet, maas_dns_server, ntp_server, default_domain,
320 failover_peer=None, subnets_dhcp_snippets=[]):320 failover_peer=None, subnets_dhcp_snippets=[]):
321 """Return DHCP subnet configuration dict for a rack interface."""321 """Return DHCP subnet configuration dict for a rack interface."""
322 ip_network = subnet.get_ipnetwork()322 ip_network = subnet.get_ipnetwork()
323 if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
324 # Replace MAAS DNS with the servers defined on the subnet.
325 dns_servers = ", ".join(subnet.dns_servers)
326 elif maas_dns_server is not None and len(maas_dns_server) > 0:
327 dns_servers = maas_dns_server
328 else:
329 dns_servers = ""
323 return {330 return {
324 'subnet': str(ip_network.network),331 'subnet': str(ip_network.network),
325 'subnet_mask': str(ip_network.netmask),332 'subnet_mask': str(ip_network.netmask),
@@ -368,12 +375,10 @@
368 from maasserver.dns.zonegenerator import get_dns_server_address375 from maasserver.dns.zonegenerator import get_dns_server_address
369376
370 try:377 try:
371 dns_servers = get_dns_server_address(378 maas_dns_server = get_dns_server_address(
372 rack_controller, ipv4=(ip_version == 4), ipv6=(ip_version == 6))379 rack_controller, ipv4=(ip_version == 4), ipv6=(ip_version == 6))
373 except UnresolvableHost:380 except UnresolvableHost:
374 # No IPv6 DNS server addresses found. As a space-separated string,381 maas_dns_server = None
375 # that becomes the empty string.
376 dns_servers = ''
377382
378 # Select the best interface for this VLAN. This is an interface that383 # Select the best interface for this VLAN. This is an interface that
379 # at least has an IP address.384 # at least has an IP address.
@@ -382,9 +387,10 @@
382 interface = get_best_interface(interfaces)387 interface = get_best_interface(interfaces)
383 if interface is None:388 if interface is None:
384 raise DHCPConfigurationError(389 raise DHCPConfigurationError(
385 "No interface on rack controller '%s' has an IP address on any "390 "No IPv%d interface on rack controller '%s' has an IP address on "
386 "subnet on VLAN '%s.%d'." % (391 "any subnet on VLAN '%s.%d'." % (
387 rack_controller.hostname, vlan.fabric.name, vlan.vid))392 ip_version, rack_controller.hostname, vlan.fabric.name,
393 vlan.vid))
388394
389 # Generate the failover peer for this VLAN.395 # Generate the failover peer for this VLAN.
390 if vlan.secondary_rack_id is not None:396 if vlan.secondary_rack_id is not None:
@@ -406,7 +412,7 @@
406 for subnet in subnets:412 for subnet in subnets:
407 subnet_configs.append(413 subnet_configs.append(
408 make_subnet_config(414 make_subnet_config(
409 rack_controller, subnet, dns_servers, ntp_server,415 rack_controller, subnet, maas_dns_server, ntp_server,
410 domain, peer_name, subnets_dhcp_snippets))416 domain, peer_name, subnets_dhcp_snippets))
411417
412 # Generate the hosts for all subnets.418 # Generate the hosts for all subnets.
@@ -470,31 +476,52 @@
470 for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items():476 for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items():
471 # IPv4477 # IPv4
472 if len(subnets_v4) > 0:478 if len(subnets_v4) > 0:
473 failover_peer, subnets, hosts, interface = get_dhcp_configure_for(479 try:
474 4, rack_controller, vlan, subnets_v4,480 config = get_dhcp_configure_for(
475 ntp_server, default_domain, dhcp_snippets)481 4, rack_controller, vlan, subnets_v4, ntp_server,
476 if failover_peer is not None:482 default_domain, dhcp_snippets)
477 failover_peers_v4.append(failover_peer)483 except DHCPConfigurationError as e:
478 shared_networks_v4.append({484 # XXX bug #1602412: this silently breaks DHCPv4, but we cannot
479 "name": "vlan-%d" % vlan.id,485 # allow it to crash here since DHCPv6 might be able to run.
480 "subnets": subnets,486 # This error may be irrelevant if there is an IPv4 network in
481 })487 # the MAAS model which is not configured on the rack, and the
482 hosts_v4.extend(hosts)488 # user only wants to serve DHCPv6. But it is still something
483 interfaces_v4.add(interface)489 # worth noting, so log it and continue.
484490 log.err(e)
491 else:
492 failover_peer, subnets, hosts, interface = config
493 if failover_peer is not None:
494 failover_peers_v4.append(failover_peer)
495 shared_networks_v4.append({
496 "name": "vlan-%d" % vlan.id,
497 "subnets": subnets,
498 })
499 hosts_v4.extend(hosts)
500 interfaces_v4.add(interface)
485 # IPv6501 # IPv6
486 if len(subnets_v6) > 0:502 if len(subnets_v6) > 0:
487 failover_peer, subnets, hosts, interface = get_dhcp_configure_for(503 try:
488 6, rack_controller, vlan, subnets_v6,504 config = get_dhcp_configure_for(
489 ntp_server, default_domain, dhcp_snippets)505 6, rack_controller, vlan, subnets_v6,
490 if failover_peer is not None:506 ntp_server, default_domain, dhcp_snippets)
491 failover_peers_v6.append(failover_peer)507 except DHCPConfigurationError as e:
492 shared_networks_v6.append({508 # XXX bug #1602412: this silently breaks DHCPv6, but we cannot
493 "name": "vlan-%d" % vlan.id,509 # allow it to crash here since DHCPv4 might be able to run.
494 "subnets": subnets,510 # This error may be irrelevant if there is an IPv6 network in
495 })511 # the MAAS model which is not configured on the rack, and the
496 hosts_v6.extend(hosts)512 # user only wants to serve DHCPv4. But it is still something
497 interfaces_v6.add(interface)513 # worth noting, so log it and continue.
514 log.err(e)
515 else:
516 failover_peer, subnets, hosts, interface = config
517 if failover_peer is not None:
518 failover_peers_v6.append(failover_peer)
519 shared_networks_v6.append({
520 "name": "vlan-%d" % vlan.id,
521 "subnets": subnets,
522 })
523 hosts_v6.extend(hosts)
524 interfaces_v6.add(interface)
498 return (525 return (
499 get_omapi_key(),526 get_omapi_key(),
500 failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4,527 failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4,
501528
=== modified file 'src/maasserver/tests/test_dhcp.py'
--- src/maasserver/tests/test_dhcp.py 2016-05-12 19:07:37 +0000
+++ src/maasserver/tests/test_dhcp.py 2016-07-15 15:53:19 +0000
@@ -45,6 +45,7 @@
45from maastesting.twisted import (45from maastesting.twisted import (
46 always_fail_with,46 always_fail_with,
47 always_succeed_with,47 always_succeed_with,
48 TwistedLoggerFixture,
48)49)
49from netaddr import (50from netaddr import (
50 IPAddress,51 IPAddress,
@@ -627,23 +628,78 @@
627 'dhcp_snippets',628 'dhcp_snippets',
628 ]))629 ]))
629630
630 def test__sets_dns_and_ntp_from_arguments(self):631 def test__sets_ipv4_dns_from_arguments(self):
631 rack_controller = factory.make_RackController(interface=False)632 rack_controller = factory.make_RackController(interface=False)
632 vlan = factory.make_VLAN()633 vlan = factory.make_VLAN()
633 subnet = factory.make_Subnet(vlan=vlan)634 subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=4)
634 factory.make_Interface(635 factory.make_Interface(
635 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)636 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
636 dns = '%s %s' % (637 maas_dns = factory.make_ipv4_address()
637 factory.make_ipv4_address(),638 ntp = factory.make_name('ntp')
638 factory.make_ipv6_address(),639 default_domain = Domain.objects.get_default_domain()
639 )640 config = dhcp.make_subnet_config(
640 ntp = factory.make_name('ntp')641 rack_controller, subnet, maas_dns, ntp, default_domain)
641 default_domain = Domain.objects.get_default_domain()642 self.expectThat(config['dns_servers'], Equals(maas_dns))
642 config = dhcp.make_subnet_config(643
643 rack_controller, subnet, dns, ntp, default_domain)644 def test__sets_ipv6_dns_from_arguments(self):
644 self.expectThat(config['dns_servers'], Equals(dns))645 rack_controller = factory.make_RackController(interface=False)
646 vlan = factory.make_VLAN()
647 subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=6)
648 factory.make_Interface(
649 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
650 maas_dns = factory.make_ipv6_address()
651 ntp = factory.make_name('ntp')
652 default_domain = Domain.objects.get_default_domain()
653 config = dhcp.make_subnet_config(
654 rack_controller, subnet, maas_dns, ntp, default_domain)
655 self.expectThat(config['dns_servers'], Equals(maas_dns))
656
657 def test__sets_ntp_from_arguments(self):
658 rack_controller = factory.make_RackController(interface=False)
659 vlan = factory.make_VLAN()
660 subnet = factory.make_Subnet(vlan=vlan, dns_servers=[])
661 factory.make_Interface(
662 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
663 ntp = factory.make_name('ntp')
664 default_domain = Domain.objects.get_default_domain()
665 config = dhcp.make_subnet_config(
666 rack_controller, subnet, "", ntp, default_domain)
645 self.expectThat(config['ntp_server'], Equals(ntp))667 self.expectThat(config['ntp_server'], Equals(ntp))
646668
669 def test__overrides_ipv4_dns_from_subnet(self):
670 rack_controller = factory.make_RackController(interface=False)
671 vlan = factory.make_VLAN()
672 subnet = factory.make_Subnet(vlan=vlan, version=4)
673 maas_dns = factory.make_ipv4_address()
674 subnet_dns_servers = ["8.8.8.8", "8.8.4.4"]
675 subnet.dns_servers = subnet_dns_servers
676 subnet.save()
677 factory.make_Interface(
678 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
679 ntp = factory.make_name('ntp')
680 default_domain = Domain.objects.get_default_domain()
681 config = dhcp.make_subnet_config(
682 rack_controller, subnet, maas_dns, ntp, default_domain)
683 self.expectThat(
684 config['dns_servers'], Equals(", ".join(subnet_dns_servers)))
685
686 def test__overrides_ipv6_dns_from_subnet(self):
687 rack_controller = factory.make_RackController(interface=False)
688 vlan = factory.make_VLAN()
689 subnet = factory.make_Subnet(vlan=vlan, version=6)
690 maas_dns = factory.make_ipv6_address()
691 subnet_dns_servers = ["2001:db8::1", "2001:db8::2"]
692 subnet.dns_servers = subnet_dns_servers
693 subnet.save()
694 factory.make_Interface(
695 INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
696 ntp = factory.make_name('ntp')
697 default_domain = Domain.objects.get_default_domain()
698 config = dhcp.make_subnet_config(
699 rack_controller, subnet, maas_dns, ntp, default_domain)
700 self.expectThat(
701 config['dns_servers'], Equals(", ".join(subnet_dns_servers)))
702
647 def test__sets_domain_name_from_passed_domain(self):703 def test__sets_domain_name_from_passed_domain(self):
648 rack_controller = factory.make_RackController(interface=False)704 rack_controller = factory.make_RackController(interface=False)
649 vlan = factory.make_VLAN()705 vlan = factory.make_VLAN()
@@ -1121,7 +1177,8 @@
1121 ha_vlan = factory.make_VLAN(1177 ha_vlan = factory.make_VLAN(
1122 dhcp_on=True, primary_rack=primary_rack,1178 dhcp_on=True, primary_rack=primary_rack,
1123 secondary_rack=secondary_rack)1179 secondary_rack=secondary_rack)
1124 ha_subnet = factory.make_ipv4_Subnet_with_IPRanges(vlan=ha_vlan)1180 ha_subnet = factory.make_ipv4_Subnet_with_IPRanges(
1181 vlan=ha_vlan, dns_servers=['127.0.0.1'])
1125 ha_network = ha_subnet.get_ipnetwork()1182 ha_network = ha_subnet.get_ipnetwork()
1126 ha_dhcp_snippets = [1183 ha_dhcp_snippets = [
1127 factory.make_DHCPSnippet(subnet=ha_subnet, enabled=True)1184 factory.make_DHCPSnippet(subnet=ha_subnet, enabled=True)
@@ -1136,7 +1193,8 @@
1136 secondary_ip = factory.make_StaticIPAddress(1193 secondary_ip = factory.make_StaticIPAddress(
1137 alloc_type=IPADDRESS_TYPE.AUTO, subnet=ha_subnet,1194 alloc_type=IPADDRESS_TYPE.AUTO, subnet=ha_subnet,
1138 interface=secondary_interface)1195 interface=secondary_interface)
1139 other_subnet = factory.make_ipv4_Subnet_with_IPRanges(vlan=ha_vlan)1196 other_subnet = factory.make_ipv4_Subnet_with_IPRanges(
1197 vlan=ha_vlan, dns_servers=['127.0.0.1'])
1140 other_network = other_subnet.get_ipnetwork()1198 other_network = other_subnet.get_ipnetwork()
1141 other_dhcp_snippets = [1199 other_dhcp_snippets = [
1142 factory.make_DHCPSnippet(subnet=other_subnet, enabled=True)1200 factory.make_DHCPSnippet(subnet=other_subnet, enabled=True)
@@ -1358,7 +1416,8 @@
1358 return cluster, cluster.ConfigureDHCPv4, cluster.ConfigureDHCPv61416 return cluster, cluster.ConfigureDHCPv4, cluster.ConfigureDHCPv6
13591417
1360 @transactional1418 @transactional
1361 def create_rack_controller(self, dhcp_on=True):1419 def create_rack_controller(
1420 self, dhcp_on=True, missing_ipv4=False, missing_ipv6=False):
1362 """Create a `rack_controller` in a state that will call both1421 """Create a `rack_controller` in a state that will call both
1363 `ConfigureDHCPv4` and `ConfigureDHCPv6` with data."""1422 `ConfigureDHCPv4` and `ConfigureDHCPv6` with data."""
1364 primary_rack = factory.make_RackController(interface=False)1423 primary_rack = factory.make_RackController(interface=False)
@@ -1382,18 +1441,20 @@
1382 subnet_v6, "fd38:c341:27da:c831:0:1::",1441 subnet_v6, "fd38:c341:27da:c831:0:1::",
1383 "fd38:c341:27da:c831:0:1:ffff:0")1442 "fd38:c341:27da:c831:0:1:ffff:0")
13841443
1385 factory.make_StaticIPAddress(1444 if not missing_ipv4:
1386 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,1445 factory.make_StaticIPAddress(
1387 interface=primary_interface)1446 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,
1388 factory.make_StaticIPAddress(1447 interface=primary_interface)
1389 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,1448 factory.make_StaticIPAddress(
1390 interface=secondary_interface)1449 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,
1391 factory.make_StaticIPAddress(1450 interface=secondary_interface)
1392 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v6,1451 if not missing_ipv6:
1393 interface=primary_interface)1452 factory.make_StaticIPAddress(
1394 factory.make_StaticIPAddress(1453 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v6,
1395 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v6,1454 interface=primary_interface)
1396 interface=secondary_interface)1455 factory.make_StaticIPAddress(
1456 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v6,
1457 interface=secondary_interface)
13971458
1398 for _ in range(3):1459 for _ in range(3):
1399 factory.make_DHCPSnippet(subnet=subnet_v4, enabled=True)1460 factory.make_DHCPSnippet(subnet=subnet_v4, enabled=True)
@@ -1448,6 +1509,26 @@
14481509
1449 @wait_for_reactor1510 @wait_for_reactor
1450 @inlineCallbacks1511 @inlineCallbacks
1512 def test__logs_DHCPConfigurationError_ipv4(self):
1513 self.patch(dhcp.settings, "DHCP_CONNECT", True)
1514 with TwistedLoggerFixture() as logger:
1515 yield deferToDatabase(
1516 self.create_rack_controller, missing_ipv4=True)
1517 self.assertDocTestMatches(
1518 "...No IPv4 interface...", logger.output)
1519
1520 @wait_for_reactor
1521 @inlineCallbacks
1522 def test__logs_DHCPConfigurationError_ipv6(self):
1523 self.patch(dhcp.settings, "DHCP_CONNECT", True)
1524 with TwistedLoggerFixture() as logger:
1525 yield deferToDatabase(
1526 self.create_rack_controller, missing_ipv6=True)
1527 self.assertDocTestMatches(
1528 "...No IPv6 interface...", logger.output)
1529
1530 @wait_for_reactor
1531 @inlineCallbacks
1451 def test__doesnt_call_configure_for_both_ipv4_and_ipv6(self):1532 def test__doesnt_call_configure_for_both_ipv4_and_ipv6(self):
1452 rack_controller, args = yield deferToDatabase(1533 rack_controller, args = yield deferToDatabase(
1453 self.create_rack_controller)1534 self.create_rack_controller)

Subscribers

People subscribed via source and target branches

to all changes: