def test_yields_internal_forward_zones(self): default_domain = Domain.objects.get_default_domain() subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) domains = [] for _ in range(3): record = InternalDomainResourseRecord( rrtype='A', rrdata=factory.pick_ip_in_Subnet(subnet)) resource = InternalDomainResourse( name=factory.make_name('resource'), records=[record]) domain = InternalDomain(name=factory.make_name('domain'), ttl=random.randint(15, 300), resources=[resource]) domains.append(domain) zones = ZoneGenerator([], [subnet], serial=random.randint(0, 65535), internal_domains=domains).as_list() self.assertThat( zones, MatchesSetwise(*[ MatchesAll( forward_zone(domain.name), MatchesStructure(_other_mapping=MatchesDict({ domain.resources[0].name: MatchesStructure(rrset=MatchesSetwise( Equals((domain.ttl, domain.resources[0].records[0].rrtype, domain.resources[0].records[0].rrdata)))) }))) for domain in domains ] + [ reverse_zone(default_domain.name, "10/29"), reverse_zone(default_domain.name, "10/24") ]))
def test_none_domain_ttl_doesnt_override_default_ttl(self): # If the domain doesn't hae a ttl, the global default ttl is used. Config.objects.set_config("default_dns_ttl", 42) domain = factory.make_Domain(ttl=None) [zone_config] = ZoneGenerator(domains=[domain], subnets=[], serial=123) self.assertEqual(domain.name, zone_config.domain) self.assertEqual(42, zone_config.default_ttl)
def test_with_child_domain_yields_glue_when_needed(self): default_domain = Domain.objects.get_default_domain().name domain = factory.make_Domain(name="henry") john = factory.make_Domain(name="john.henry") subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) sip = factory.make_StaticIPAddress(subnet=subnet) factory.make_Node_with_Interface_on_Subnet( subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric ) factory.make_DNSResource(name="ns", domain=john, ip_addresses=[sip]) factory.make_DNSData(name="@", domain=john, rrtype="NS", rrdata="ns") # We have a subdomain (john.henry) which as an NS RR of # 'ns.john.henry', and we should see glue records for it in the parent # zone, as well as the A RR in the child. zones = ZoneGenerator( domain, subnet, serial=random.randint(0, 65535) ).as_list() self.assertThat( zones, MatchesSetwise( forward_zone("henry"), reverse_zone(default_domain, "10/29"), reverse_zone(default_domain, "10/24"), ), ) expected_map = { "john": HostnameRRsetMapping( None, {(30, "NS", default_domain), (30, "NS", "ns")} ), "ns": HostnameRRsetMapping(None, {(30, "A", sip.ip)}), } self.assertEqual(expected_map, zones[0]._other_mapping)
def test_dnsdata_inherits_domain(self): # If there is no ttl on the DNSData, but is on Domain, then we get the # domain value. global_ttl = random.randint(100, 199) Config.objects.set_config("default_dns_ttl", global_ttl) subnet = factory.make_Subnet(cidr="10.0.0.0/23") domain = factory.make_Domain(ttl=random.randint(200, 299)) dnsrr = factory.make_DNSResource( no_ip_addresses=True, domain=domain, address_ttl=random.randint(400, 499), ) dnsdata = factory.make_DNSData(dnsresource=dnsrr) expected_forward = { dnsrr.name: HostnameRRsetMapping( None, {(domain.ttl, dnsdata.rrtype, dnsdata.rrdata)} ) } zones = ZoneGenerator( domain, subnet, default_ttl=global_ttl, serial=random.randint(0, 65535), ).as_list() self.assertEqual(expected_forward, zones[0]._other_mapping) self.assertEqual({}, zones[0]._mapping) self.assertEqual({}, zones[1]._mapping) self.assertEqual(None, dnsdata.ttl)
def test_node_ttl_overrides_domain(self): global_ttl = random.randint(100, 199) Config.objects.set_config("default_dns_ttl", global_ttl) subnet = factory.make_Subnet(cidr="10.0.0.0/23") domain = factory.make_Domain(ttl=random.randint(200, 299)) node = factory.make_Node_with_Interface_on_Subnet( status=NODE_STATUS.READY, subnet=subnet, domain=domain, address_ttl=random.randint(300, 399), ) boot_iface = node.get_boot_interface() [boot_ip] = boot_iface.claim_auto_ips() expected_forward = { node.hostname: HostnameIPMapping( node.system_id, node.address_ttl, {boot_ip.ip}, node.node_type ) } expected_reverse = { node.fqdn: HostnameIPMapping( node.system_id, node.address_ttl, {boot_ip.ip}, node.node_type ) } zones = ZoneGenerator( domain, subnet, default_ttl=global_ttl, serial=random.randint(0, 65535), ).as_list() self.assertEqual(expected_forward, zones[0]._mapping) self.assertEqual(expected_reverse, zones[1]._mapping)
def test_dnsresource_address_overrides_domain(self): # DNSResource.address_ttl _does_, however, override Domain.ttl for # addresses that do not have nodes associated with them. global_ttl = random.randint(100, 199) Config.objects.set_config('default_dns_ttl', global_ttl) subnet = factory.make_Subnet(cidr="10.0.0.0/23") domain = factory.make_Domain(ttl=random.randint(200, 299)) node = factory.make_Node_with_Interface_on_Subnet( status=NODE_STATUS.READY, subnet=subnet, domain=domain, address_ttl=random.randint(300, 399)) boot_iface = node.get_boot_interface() [boot_ip] = boot_iface.claim_auto_ips() dnsrr = factory.make_DNSResource( domain=domain, address_ttl=random.randint(400, 499)) node_ips = {boot_ip.ip} dnsrr_ips = { ip.ip for ip in dnsrr.ip_addresses.all() if ip is not None} expected_forward = { node.hostname: HostnameIPMapping( node.system_id, node.address_ttl, node_ips, node.node_type), dnsrr.name: HostnameIPMapping( None, dnsrr.address_ttl, dnsrr_ips, None, dnsrr.id), } expected_reverse = { node.fqdn: HostnameIPMapping( node.system_id, node.address_ttl, node_ips, node.node_type, None), dnsrr.fqdn: HostnameIPMapping( None, dnsrr.address_ttl, dnsrr_ips, None, dnsrr.id)} zones = ZoneGenerator( domain, subnet, default_ttl=global_ttl, serial=random.randint(0, 65535)).as_list() self.assertEqual(expected_forward, zones[0]._mapping) self.assertEqual(expected_reverse, zones[1]._mapping)
def test_dnsdata_overrides_domain(self): # If DNSData has a ttl, we use that in preference to anything else. global_ttl = random.randint(100, 199) Config.objects.set_config("default_dns_ttl", global_ttl) subnet = factory.make_Subnet(cidr="10.0.0.0/23") domain = factory.make_Domain(ttl=random.randint(200, 299)) dnsrr = factory.make_DNSResource( no_ip_addresses=True, domain=domain, address_ttl=random.randint(400, 499), ) dnsdata = factory.make_DNSData( dnsresource=dnsrr, ttl=random.randint(500, 599) ) expected_forward = { dnsrr.name: HostnameRRsetMapping( None, {(dnsdata.ttl, dnsdata.rrtype, dnsdata.rrdata)} ) } zones = ZoneGenerator( domain, subnet, default_ttl=global_ttl, serial=random.randint(0, 65535), ).as_list() self.assertEqual(expected_forward, zones[0]._other_mapping) self.assertEqual({}, zones[0]._mapping) self.assertEqual({}, zones[1]._mapping)
def test_yields_forward_and_reverse_zone_no_overlap_bug(self): domain = factory.make_Domain(name="overlap") subnet1 = factory.make_Subnet(cidr="192.168.33.0/25") subnet2 = factory.make_Subnet(cidr="192.168.35.0/26") subnet3 = factory.make_Subnet(cidr="192.168.36.0/26") zones = ZoneGenerator( domain, [ subnet2, subnet1, subnet3, ], # purposely out of order to assert subnets are being sorted serial=random.randint(0, 65535), ).as_list() expected_domains = [ "0-25.33.168.192.in-addr.arpa", "0-26.35.168.192.in-addr.arpa", "0-26.36.168.192.in-addr.arpa", "33.168.192.in-addr.arpa", "35.168.192.in-addr.arpa", "36.168.192.in-addr.arpa", "overlap", ] zone_names = [ info.zone_name for zone in zones for info in zone.zone_info ] self.assertCountEqual(zone_names, expected_domains)
def test_supernet_inherits_rfc2317_net(self): domain = Domain.objects.get_default_domain() subnet1 = factory.make_Subnet(host_bits=2) net = IPNetwork(subnet1.cidr) if net.version == 6: prefixlen = random.randint(121, 124) else: prefixlen = random.randint(22, 24) parent = IPNetwork("%s/%d" % (net.network, prefixlen)) parent = IPNetwork("%s/%d" % (parent.network, prefixlen)) subnet2 = factory.make_Subnet(cidr=parent) node = factory.make_Node_with_Interface_on_Subnet( subnet=subnet1, vlan=subnet1.vlan, fabric=subnet1.vlan.fabric, domain=domain) boot_iface = node.boot_interface factory.make_StaticIPAddress(interface=boot_iface, subnet=subnet1) default_ttl = random.randint(10, 300) Config.objects.set_config('default_dns_ttl', default_ttl) zones = ZoneGenerator(domain, [subnet1, subnet2], default_ttl=default_ttl, serial=random.randint(0, 65535)).as_list() self.assertThat( zones, MatchesSetwise(forward_zone(domain.name), reverse_zone(domain.name, subnet1.cidr), reverse_zone(domain.name, subnet2.cidr))) self.assertEqual(set(), zones[1]._rfc2317_ranges) self.assertEqual({net}, zones[2]._rfc2317_ranges)
def test_parent_of_default_domain_gets_glue(self): default_domain = Domain.objects.get_default_domain() default_domain.name = 'maas.example.com' default_domain.save() domains = [default_domain, factory.make_Domain('example.com')] self.patch(zonegenerator, 'get_dns_server_addresses').return_value = [ IPAddress('5.5.5.5') ] subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) factory.make_StaticIPAddress(subnet=subnet) factory.make_Node_with_Interface_on_Subnet(subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric) zones = ZoneGenerator(domains, subnet, serial=random.randint(0, 65535)).as_list() self.assertThat( zones, MatchesSetwise(forward_zone(domains[0].name), forward_zone(domains[1].name), reverse_zone(domains[0].name, "10/29"), reverse_zone(domains[0].name, "10/24"))) # maas.example.com is the default zone, and has an A RR for its NS RR. # example.com has NS maas.example.com., and a glue record for that. expected_map_0 = { '@': HostnameRRsetMapping(None, {(30, 'A', '5.5.5.5')}, None) } expected_map_1 = { 'maas': HostnameRRsetMapping(None, {(30, 'A', IPAddress('5.5.5.5')), (30, 'NS', 'maas.example.com')}, None) } self.assertEqual(expected_map_0, zones[0]._other_mapping) self.assertEqual(expected_map_1, zones[1]._other_mapping)
def test_dnsresource_address_does_not_affect_addresses_when_node_set(self): # If a node has the same FQDN as a DNSResource, then we use whatever # address_ttl there is on the Node (whether None, or not) rather than # that on any DNSResource addresses with the same FQDN. global_ttl = random.randint(100, 199) Config.objects.set_config('default_dns_ttl', global_ttl) subnet = factory.make_Subnet(cidr="10.0.0.0/23") domain = factory.make_Domain(ttl=random.randint(200, 299)) node = factory.make_Node_with_Interface_on_Subnet( status=NODE_STATUS.READY, subnet=subnet, domain=domain, address_ttl=random.randint(300, 399)) boot_iface = node.get_boot_interface() [boot_ip] = boot_iface.claim_auto_ips() dnsrr = factory.make_DNSResource( name=node.hostname, domain=domain, address_ttl=random.randint(400, 499)) ips = { ip.ip for ip in dnsrr.ip_addresses.all() if ip is not None} ips.add(boot_ip.ip) expected_forward = {node.hostname: HostnameIPMapping( node.system_id, node.address_ttl, ips, node.node_type, dnsrr.id)} expected_reverse = { node.fqdn: HostnameIPMapping( node.system_id, node.address_ttl, ips, node.node_type, dnsrr.id)} zones = ZoneGenerator( domain, subnet, default_ttl=global_ttl, serial=random.randint(0, 65535)).as_list() self.assertEqual(expected_forward, zones[0]._mapping) self.assertEqual(expected_reverse, zones[1]._mapping)
def test_domain_ttl_overrides_default_ttl(self): # If the domain has a ttl, we use that as the default ttl. Config.objects.set_config("default_dns_ttl", 42) domain = factory.make_Domain(ttl=84) [zone_config] = ZoneGenerator(domains=[domain], subnets=[], serial=123) self.assertEqual(domain.name, zone_config.domain) self.assertEqual(domain.ttl, zone_config.default_ttl)
def test_two_managed_interfaces_yields_one_forward_two_reverse_zones(self): default_domain = Domain.objects.get_default_domain().name domain = factory.make_Domain() subnet1 = factory.make_Subnet() subnet2 = factory.make_Subnet() expected_zones = [ forward_zone(domain.name), reverse_zone(default_domain, subnet1.cidr), reverse_zone(default_domain, subnet2.cidr), ] subnets = Subnet.objects.all() expected_zones = ( [forward_zone(domain.name)] + [ reverse_zone(default_domain, subnet.get_ipnetwork()) for subnet in subnets ] + [ reverse_zone( default_domain, self.rfc2317_network(subnet.get_ipnetwork()), ) for subnet in subnets if self.rfc2317_network(subnet.get_ipnetwork()) is not None ] ) self.assertThat( ZoneGenerator( domain, [subnet1, subnet2], serial=random.randint(0, 65535) ).as_list(), MatchesSetwise(*expected_zones), )
def dns_update_all_zones(reload_retry=False, reload_timeout=2): """Update all zone files for all domains. Serving these zone files means updating BIND's configuration to include them, then asking it to load the new configuration. :param reload_retry: Should the DNS server reload be retried in case of failure? Defaults to `False`. :type reload_retry: bool """ if not is_dns_enabled(): return domains = Domain.objects.filter(authoritative=True) forwarded_zones = forward_domains_to_forwarded_zones( Domain.objects.get_forward_domains()) subnets = Subnet.objects.exclude(rdns_mode=RDNS_MODE.DISABLED) default_ttl = Config.objects.get_config("default_dns_ttl") serial = current_zone_serial() zones = ZoneGenerator( domains, subnets, default_ttl, serial, internal_domains=[get_internal_domain()], ).as_list() bind_write_zones(zones) # We should not be calling bind_write_options() here; call-sites should be # making a separate call. It's a historical legacy, where many sites now # expect this side-effect from calling dns_update_all_zones_now(), and # some that call it for this side-effect alone. At present all it does is # set the upstream DNS servers, nothing to do with serving zones at all! bind_write_options( upstream_dns=get_upstream_dns(), dnssec_validation=get_dnssec_validation(), ) # Nor should we be rewriting ACLs that are related only to allowing # recursive queries to the upstream DNS servers. Again, this is legacy, # where the "trusted" ACL ended up in the same configuration file as the # zone stanzas, and so both need to be rewritten at the same time. bind_write_configuration( zones, trusted_networks=get_trusted_networks(), forwarded_zones=forwarded_zones, ) # Reloading with retries may be a legacy from Celery days, or it may be # necessary to recover from races during start-up. We're not sure if it is # actually needed but it seems safer to maintain this behaviour until we # have a better understanding. if reload_retry: reloaded = bind_reload_with_retries(timeout=reload_timeout) else: reloaded = bind_reload(timeout=reload_timeout) # Return the current serial and list of domain names. return serial, reloaded, [domain.name for domain in domains]
def test_returns_interface_ips_but_no_nulls(self): default_domain = Domain.objects.get_default_domain().name domain = factory.make_Domain(name='henry') subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) subnet.gateway_ip = str(IPAddress(IPNetwork(subnet.cidr).ip + 1)) subnet.save() # Create a node with two interfaces, with NULL ips node = factory.make_Node_with_Interface_on_Subnet( subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric, domain=domain, interface_count=3) dnsdata = factory.make_DNSData(domain=domain) boot_iface = node.boot_interface interfaces = list(node.interface_set.all().exclude(id=boot_iface.id)) # Now go add IP addresses to the boot interface, and one other boot_ip = factory.make_StaticIPAddress(interface=boot_iface, subnet=subnet) sip = factory.make_StaticIPAddress(interface=interfaces[0], subnet=subnet) default_ttl = random.randint(10, 300) Config.objects.set_config('default_dns_ttl', default_ttl) zones = ZoneGenerator(domain, subnet, default_ttl=default_ttl, serial=random.randint(0, 65535)).as_list() self.assertThat( zones, MatchesSetwise(forward_zone("henry"), reverse_zone(default_domain, "10/29"), reverse_zone(default_domain, "10/24"))) self.assertEqual( { node.hostname: HostnameIPMapping(node.system_id, default_ttl, {'%s' % boot_ip.ip}, node.node_type), "%s.%s" % (interfaces[0].name, node.hostname): HostnameIPMapping(node.system_id, default_ttl, {'%s' % sip.ip}, node.node_type) }, zones[0]._mapping) self.assertEqual({ dnsdata.dnsresource.name: HostnameRRsetMapping( None, {(default_ttl, dnsdata.rrtype, dnsdata.rrdata)}) }.items(), zones[0]._other_mapping.items()) self.assertEqual( { node.fqdn: HostnameIPMapping(node.system_id, default_ttl, {'%s' % boot_ip.ip}, node.node_type), '%s.%s' % (interfaces[0].name, node.fqdn): HostnameIPMapping(node.system_id, default_ttl, {'%s' % sip.ip}, node.node_type) }, zones[1]._mapping) self.assertEqual({}, zones[2]._mapping)
def test_yields_forward_and_reverse_zone(self): default_domain = Domain.objects.get_default_domain().name domain = factory.make_Domain(name='henry') subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) zones = ZoneGenerator(domain, subnet, serial=random.randint(0, 65535)).as_list() self.assertThat( zones, MatchesSetwise(forward_zone("henry"), reverse_zone(default_domain, "10/29"), reverse_zone(default_domain, "10/24")))
def test_with_node_yields_fwd_and_rev_zone(self): default_domain = Domain.objects.get_default_domain().name domain = factory.make_Domain(name='henry') subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) factory.make_Node_with_Interface_on_Subnet( subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric) zones = ZoneGenerator( domain, subnet, serial=random.randint(0, 65535)).as_list() self.assertThat( zones, MatchesSetwise( forward_zone("henry"), reverse_zone(default_domain, "10/29"), reverse_zone(default_domain, "10/24")))
def test_zone_generator_handles_rdns_mode_equal_enabled(self): Domain.objects.get_or_create(name="one") subnet = factory.make_Subnet(cidr="10.0.0.0/29") subnet.rdns_mode = RDNS_MODE.ENABLED subnet.save() default_domain = Domain.objects.get_default_domain() domains = Domain.objects.filter(name="one") subnets = Subnet.objects.all() expected_zones = ( forward_zone("one"), reverse_zone(default_domain.name, "10/29"), ) self.assertThat( ZoneGenerator(domains, subnets, serial=random.randint(0, 65535)).as_list(), MatchesSetwise(*expected_zones))
def test_forward_zone_includes_subnets_with_allow_dns_false(self): default_ttl = random.randint(10, 300) Config.objects.set_config("default_dns_ttl", default_ttl) default_domain = Domain.objects.get_default_domain() subnet = factory.make_Subnet(cidr="10.10.0.0/24", allow_dns=False) ip = factory.make_StaticIPAddress(subnet=subnet) resolver = self.patch(server_address, "resolve_hostname") resolver.return_value = {IPAddress(ip.ip)} zones = ZoneGenerator([default_domain], subnet, serial=random.randint(0, 65535)) [forward_zone] = [ zone for zone in zones if isinstance(zone, DNSForwardZoneConfig) ] self.assertEqual(forward_zone._other_mapping["@"].rrset, {(default_ttl, "A", ip.ip)})
def test_with_child_domain_yields_delegation(self): default_domain = Domain.objects.get_default_domain().name domain = factory.make_Domain(name='henry') factory.make_Domain(name="john.henry") subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr)) factory.make_Node_with_Interface_on_Subnet( subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric) zones = ZoneGenerator( domain, subnet, serial=random.randint(0, 65535)).as_list() self.assertThat( zones, MatchesSetwise( forward_zone("henry"), reverse_zone(default_domain, "10/29"), reverse_zone(default_domain, "10/24"))) expected_map = {'john': HostnameRRsetMapping( None, {(30, 'NS', default_domain)})} self.assertEqual(expected_map, zones[0]._other_mapping)
def test_yields_forward_and_reverse_zone_no_overlap(self): domain = factory.make_Domain(name="overlap") vlan1 = factory.make_VLAN(vid=1, dhcp_on=True) vlan2 = factory.make_VLAN(vid=2, dhcp_on=True) subnet1 = factory.make_Subnet(cidr="10.232.36.0/24", vlan=vlan1) subnet2 = factory.make_Subnet(cidr="10.232.32.0/21", vlan=vlan2) subnet3 = factory.make_Subnet(cidr="10.232.6.0/24", vlan=vlan1) subnet4 = factory.make_Subnet(cidr="10.2.36.0/24", vlan=vlan1) subnet5 = factory.make_Subnet(cidr="10.231.36.0/24", vlan=vlan1) subnet6 = factory.make_Subnet(cidr="10.232.40.0/24", vlan=vlan1) zones = ZoneGenerator( domain, [ subnet2, subnet1, subnet3, subnet4, subnet5, subnet6, ], # purposely out of order to assert subnets are being sorted serial=random.randint(0, 65535), ).as_list() self.assertEqual(len(zones), 7) expected_domains = [ "overlap", "36.232.10.in-addr.arpa", "32.232.10.in-addr.arpa", "6.232.10.in-addr.arpa", "36.2.10.in-addr.arpa", "36.231.10.in-addr.arpa", "40.232.10.in-addr.arpa", "38.232.10.in-addr.arpa", "34.232.10.in-addr.arpa", "35.232.10.in-addr.arpa", "39.232.10.in-addr.arpa", "37.232.10.in-addr.arpa", "33.232.10.in-addr.arpa", ] zone_names = [ info.zone_name for zone in zones for info in zone.zone_info ] self.assertCountEqual(zone_names, expected_domains)
def test_with_many_yields_many_zones(self): # This demonstrates ZoneGenerator in all-singing all-dancing mode. default_domain = Domain.objects.get_default_domain() domains = [default_domain] + [factory.make_Domain() for _ in range(3)] for _ in range(3): factory.make_Subnet() subnets = Subnet.objects.all() expected_zones = set() for domain in domains: expected_zones.add(forward_zone(domain.name)) for subnet in subnets: expected_zones.add(reverse_zone(default_domain.name, subnet.cidr)) rfc2317_net = self.rfc2317_network(subnet.get_ipnetwork()) if rfc2317_net is not None: expected_zones.add( reverse_zone(default_domain.name, rfc2317_net.cidr) ) actual_zones = ZoneGenerator( domains, subnets, serial=random.randint(0, 65535) ).as_list() self.assertThat(actual_zones, MatchesSetwise(*expected_zones))
def test_empty_yields_nothing(self): self.assertEqual( [], ZoneGenerator((), (), serial=random.randint(0, 65535)).as_list(), )
def test_defaults_ttl(self): zonegen = ZoneGenerator((), (), serial=random.randint(0, 65535)) self.assertEqual( Config.objects.get_config("default_dns_ttl"), zonegen.default_ttl ) self.assertEqual([], zonegen.as_list())
def test_accepts_default_ttl(self): default_ttl = random.randint(10, 1000) zonegen = ZoneGenerator( (), (), default_ttl=default_ttl, serial=random.randint(0, 65535) ) self.assertEqual(default_ttl, zonegen.default_ttl)