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

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 5169
Proposed branch: lp:~mpontillo/maas/subnet-dhcp-dns-settings--bug-1600720
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 332 lines (+170/-62)
2 files modified
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
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+299754@code.launchpad.net

Commit message

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
Gavin Panella (allenap) wrote :

Several comments, but the only one I'm really concerned about is the "may be irrelevant" one. With my limited context it seems to me that it ought to be more precise.

review: Needs Information
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Thanks for taking a look. Some replies below.

Revision history for this message
Gavin Panella (allenap) wrote :

Thanks for the explanation; it all makes sense now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/dhcp.py'
--- src/maasserver/dhcp.py 2016-04-13 13:18:40 +0000
+++ src/maasserver/dhcp.py 2016-07-12 20:50:38 +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-06-21 10:29:11 +0000
+++ src/maasserver/tests/test_dhcp.py 2016-07-12 20:50:38 +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)