Merge ~cgrabowski/maas:per_zone_dns_forwarders into maas:master

Proposed by Christian Grabowski
Status: Merged
Approved by: Christian Grabowski
Approved revision: 4f028a6f777f468ec4ad546068a552a492ed46e6
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:per_zone_dns_forwarders
Merge into: maas:master
Diff against target: 549 lines (+285/-6)
15 files modified
src/maasserver/api/domains.py (+6/-0)
src/maasserver/dns/config.py (+22/-1)
src/maasserver/dns/tests/test_config.py (+22/-0)
src/maasserver/forms/domain.py (+28/-0)
src/maasserver/forms/tests/test_domain.py (+32/-0)
src/maasserver/migrations/maasserver/0242_forwarddnsserver.py (+42/-0)
src/maasserver/models/__init__.py (+2/-0)
src/maasserver/models/domain.py (+24/-0)
src/maasserver/models/forwarddnsserver.py (+33/-0)
src/maasserver/models/tests/test_domain.py (+20/-0)
src/maasserver/testing/factory.py (+10/-0)
src/provisioningserver/dns/actions.py (+2/-2)
src/provisioningserver/dns/config.py (+5/-1)
src/provisioningserver/dns/tests/test_config.py (+23/-1)
src/provisioningserver/templates/dns/named.conf.template (+14/-1)
Reviewer Review Type Date Requested Status
Alexsander de Souza Approve
MAAS Lander Approve
Review via email: mp+404251@code.launchpad.net

Commit message

add forwarded_zones to config

fix form validation unit

add param comments

fix form validation

move forwarddnsserver into its own model to get around bad migration

add test case for no authoritative and forward servers

switch forward_dns_servers to IP type

add forward_dns_servers to domain form

add forward_dns_servers field to domain model

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10253/console
COMMIT: d3077cac07a6f9ce53ea495d9c6e8b83d9d75b3a

review: Needs Fixing
3590a2c... by Christian Grabowski

format changes

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10254/console
COMMIT: 3590a2ca641f2b3e303db4320c699cb836b49d5c

review: Needs Fixing
4f028a6... by Christian Grabowski

fix migration collisions

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10256/console
COMMIT: 72f0180fee999864c6c9246baefa0d85fa1135b0

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10257/console
COMMIT: 04ffac1779589494625eba967bdc3463c46bdd7e

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10258/console
COMMIT: 606afce58303ed689286496c9e243801f3cbfba2

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci.internal:8080/job/maas/job/branch-tester/10264/console
COMMIT: f3d586c66f3dfce64d562994093df4d681a1e220

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b per_zone_dns_forwarders lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 4f028a6f777f468ec4ad546068a552a492ed46e6

review: Approve
Revision history for this message
Alexsander de Souza (alexsander-souza) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/domains.py b/src/maasserver/api/domains.py
2index 51417c6..a88fe88 100644
3--- a/src/maasserver/api/domains.py
4+++ b/src/maasserver/api/domains.py
5@@ -62,6 +62,9 @@ class DomainsHandler(OperationsHandler):
6 @param (string) "authoritative" [required=false] Class type of the
7 domain.
8
9+ @param (string) "forward_dns_servers" [required=false] List of forward dns
10+ server IP addresses when MAAS is not authorititative.
11+
12 @success (http-status-code) "server-success" 200
13 @success (json) "success-json" A JSON object containing the new domain
14 object.
15@@ -175,6 +178,9 @@ class DomainHandler(OperationsHandler):
16
17 @param (string) "ttl" [required=false] The default TTL for this domain.
18
19+ @param (string) "forward_dns_servers" [required=false] List of IP addresses for
20+ forward DNS servers when MAAS is not authoritative for this domain.
21+
22 @success (http-status-code) "server-success" 200
23 @success (json) "success-json" A JSON object containing
24 information about the updated domain.
25diff --git a/src/maasserver/dns/config.py b/src/maasserver/dns/config.py
26index dedfb5f..38693a2 100644
27--- a/src/maasserver/dns/config.py
28+++ b/src/maasserver/dns/config.py
29@@ -47,6 +47,20 @@ def dns_force_reload():
30 DNSPublication(source="Force reload").save()
31
32
33+def forward_domains_to_forwarded_zones(forward_domains):
34+ # converted to a list of tuple to keep model within maasserver code
35+ return [
36+ (
37+ domain.name,
38+ [
39+ fwd_dns_srvr.ip_address
40+ for fwd_dns_srvr in domain.forward_dns_servers
41+ ],
42+ )
43+ for domain in forward_domains
44+ ]
45+
46+
47 def dns_update_all_zones(reload_retry=False, reload_timeout=2):
48 """Update all zone files for all domains.
49
50@@ -61,6 +75,9 @@ def dns_update_all_zones(reload_retry=False, reload_timeout=2):
51 return
52
53 domains = Domain.objects.filter(authoritative=True)
54+ forwarded_zones = forward_domains_to_forwarded_zones(
55+ Domain.objects.get_forward_domains()
56+ )
57 subnets = Subnet.objects.exclude(rdns_mode=RDNS_MODE.DISABLED)
58 default_ttl = Config.objects.get_config("default_dns_ttl")
59 serial = current_zone_serial()
60@@ -87,7 +104,11 @@ def dns_update_all_zones(reload_retry=False, reload_timeout=2):
61 # recursive queries to the upstream DNS servers. Again, this is legacy,
62 # where the "trusted" ACL ended up in the same configuration file as the
63 # zone stanzas, and so both need to be rewritten at the same time.
64- bind_write_configuration(zones, trusted_networks=get_trusted_networks())
65+ bind_write_configuration(
66+ zones,
67+ trusted_networks=get_trusted_networks(),
68+ forwarded_zones=forwarded_zones,
69+ )
70
71 # Reloading with retries may be a legacy from Celery days, or it may be
72 # necessary to recover from races during start-up. We're not sure if it is
73diff --git a/src/maasserver/dns/tests/test_config.py b/src/maasserver/dns/tests/test_config.py
74index c1f4e3f..7dc69c9 100644
75--- a/src/maasserver/dns/tests/test_config.py
76+++ b/src/maasserver/dns/tests/test_config.py
77@@ -27,6 +27,7 @@ from maasserver.dns.config import (
78 current_zone_serial,
79 dns_force_reload,
80 dns_update_all_zones,
81+ forward_domains_to_forwarded_zones,
82 get_internal_domain,
83 get_resource_name_for_subnet,
84 get_trusted_acls,
85@@ -78,6 +79,27 @@ class TestDNSUtilities(MAASServerTestCase):
86 MatchesStructure.byEquality(source="Force reload"),
87 )
88
89+ def test_forward_domains_to_forwarded_zones(self):
90+ name1 = factory.make_name("domain")
91+ name2 = factory.make_name("other")
92+ domain1 = factory.make_Domain(name=name1)
93+ domain2 = factory.make_Domain(name=name2)
94+ ip1 = factory.make_ip_address()
95+ ip2 = factory.make_ip_address()
96+ fwd_srvr1 = factory.make_ForwardDNSServer(
97+ ip_address=ip1, domains=[domain1]
98+ )
99+ fwd_srvr2 = factory.make_ForwardDNSServer(
100+ ip_address=ip2, domains=[domain2]
101+ )
102+ fwd_zones = forward_domains_to_forwarded_zones(
103+ Domain.objects.get_forward_domains()
104+ )
105+ self.assertItemsEqual(
106+ fwd_zones,
107+ [(name1, [fwd_srvr1.ip_address]), (name2, [fwd_srvr2.ip_address])],
108+ )
109+
110
111 class TestDNSServer(MAASServerTestCase):
112 """A base class to perform real-world DNS-related tests.
113diff --git a/src/maasserver/forms/domain.py b/src/maasserver/forms/domain.py
114index 05ab79c..9a81f9f 100644
115--- a/src/maasserver/forms/domain.py
116+++ b/src/maasserver/forms/domain.py
117@@ -5,9 +5,12 @@
118
119
120 from django import forms
121+from django.core.exceptions import ValidationError
122
123+from maasserver.fields import IPListFormField
124 from maasserver.forms import APIEditMixin, MAASModelForm
125 from maasserver.models.domain import Domain
126+from maasserver.models.forwarddnsserver import ForwardDNSServer
127
128
129 class DomainForm(MAASModelForm):
130@@ -21,6 +24,31 @@ class DomainForm(MAASModelForm):
131
132 authoritative = forms.NullBooleanField(required=False)
133
134+ forward_dns_servers = IPListFormField(required=False)
135+
136+ def save(self):
137+ super(MAASModelForm, self).save()
138+ fwd_srvrs = self.cleaned_data.get("forward_dns_servers")
139+ if fwd_srvrs is not None:
140+ fwd_srvrs_list = fwd_srvrs.split(" ")
141+ for fwd_srvr_ip in fwd_srvrs_list:
142+ fwd_srvr = ForwardDNSServer.objects.get_or_create(
143+ ip_address=fwd_srvr_ip
144+ )[0]
145+ fwd_srvr.domains.add(self.instance)
146+ fwd_srvr.save()
147+ del self.cleaned_data["forward_dns_servers"]
148+ return self.instance
149+
150+ def clean(self):
151+ if self.data.get("authoritative") and len(
152+ self.data.get("forward_dns_servers", "")
153+ ):
154+ raise ValidationError(
155+ "a domain cannot be both authoritative and have forward dns servers"
156+ )
157+ super(DomainForm, self).clean()
158+
159 def _post_clean(self):
160 # ttl=None needs to make it through. See also APIEditMixin
161 self.cleaned_data = {
162diff --git a/src/maasserver/forms/tests/test_domain.py b/src/maasserver/forms/tests/test_domain.py
163index c423600..f50f780 100644
164--- a/src/maasserver/forms/tests/test_domain.py
165+++ b/src/maasserver/forms/tests/test_domain.py
166@@ -84,3 +84,35 @@ class TestDomainForm(MAASServerTestCase):
167 self.assertEqual(name, domain.name)
168 self.assertEqual(authoritative, domain.authoritative)
169 self.assertEqual(None, domain.ttl)
170+
171+ def test_can_create_forward_dns_server(self):
172+ name = factory.make_name("domain")
173+ forward_dns_servers = [factory.make_ip_address() for _ in range(0, 2)]
174+ form = DomainForm(
175+ {
176+ "name": name,
177+ "authoritative": False,
178+ "forward_dns_servers": " ".join(forward_dns_servers),
179+ }
180+ )
181+ self.assertTrue(form.is_valid(), form.errors)
182+ domain = form.save()
183+ self.assertEqual(
184+ forward_dns_servers,
185+ [
186+ fwd_dns_srvr.ip_address
187+ for fwd_dns_srvr in domain.forward_dns_servers
188+ ],
189+ )
190+
191+ def test_validate_authority(self):
192+ name = factory.make_name("domain")
193+ forward_dns_servers = [factory.make_ip_address() for _ in range(0, 2)]
194+ form = DomainForm(
195+ {
196+ "name": name,
197+ "authoritative": True,
198+ "forward_dns_servers": " ".join(forward_dns_servers),
199+ }
200+ )
201+ self.assertRaises(ValueError, form.save)
202diff --git a/src/maasserver/migrations/maasserver/0242_forwarddnsserver.py b/src/maasserver/migrations/maasserver/0242_forwarddnsserver.py
203new file mode 100644
204index 0000000..dc5fdd9
205--- /dev/null
206+++ b/src/maasserver/migrations/maasserver/0242_forwarddnsserver.py
207@@ -0,0 +1,42 @@
208+# Generated by Django 2.2.12 on 2021-06-14 23:05
209+
210+from django.db import migrations, models
211+
212+import maasserver.models.cleansave
213+
214+
215+class Migration(migrations.Migration):
216+
217+ dependencies = [
218+ ("maasserver", "0241_physical_interface_default_node_numanode"),
219+ ]
220+
221+ operations = [
222+ migrations.CreateModel(
223+ name="ForwardDNSServer",
224+ fields=[
225+ (
226+ "id",
227+ models.AutoField(
228+ auto_created=True,
229+ primary_key=True,
230+ serialize=False,
231+ verbose_name="ID",
232+ ),
233+ ),
234+ ("created", models.DateTimeField(editable=False)),
235+ ("updated", models.DateTimeField(editable=False)),
236+ (
237+ "ip_address",
238+ models.GenericIPAddressField(
239+ default=None, editable=False, unique=True
240+ ),
241+ ),
242+ ("domains", models.ManyToManyField(to="maasserver.Domain")),
243+ ],
244+ options={
245+ "abstract": False,
246+ },
247+ bases=(maasserver.models.cleansave.CleanSave, models.Model),
248+ ),
249+ ]
250diff --git a/src/maasserver/models/__init__.py b/src/maasserver/models/__init__.py
251index e3f96e6..29904d0 100644
252--- a/src/maasserver/models/__init__.py
253+++ b/src/maasserver/models/__init__.py
254@@ -34,6 +34,7 @@ __all__ = [
255 "FileStorage",
256 "Filesystem",
257 "FilesystemGroup",
258+ "ForwardDNSServer",
259 "GlobalDefault",
260 "Interface",
261 "IPRange",
262@@ -136,6 +137,7 @@ from maasserver.models.filesystemgroup import (
263 VMFS,
264 VolumeGroup,
265 )
266+from maasserver.models.forwarddnsserver import ForwardDNSServer
267 from maasserver.models.globaldefault import GlobalDefault
268 from maasserver.models.interface import (
269 BondInterface,
270diff --git a/src/maasserver/models/domain.py b/src/maasserver/models/domain.py
271index a131a5b..6e510e8 100644
272--- a/src/maasserver/models/domain.py
273+++ b/src/maasserver/models/domain.py
274@@ -97,6 +97,15 @@ class DomainManager(Manager, DomainQueriesMixin):
275
276 return GlobalDefault.objects.instance().domain
277
278+ def get_forward_domains(self):
279+ rows = self.raw(
280+ """SELECT * FROM maasserver_domain domain
281+ WHERE EXISTS (
282+ SELECT id FROM maasserver_forwarddnsserver_domains WHERE domain_id = domain.id
283+ );"""
284+ )
285+ return list(rows)
286+
287 def get_or_create_default_domain(self):
288 """Return the default domain."""
289 now = datetime.datetime.now()
290@@ -240,6 +249,13 @@ class Domain(CleanSave, TimestampedModel):
291 count += len(resource.dnsdata_set.all())
292 return count
293
294+ @property
295+ def forward_dns_servers(self):
296+ # avoid circular import
297+ from maasserver.models.forwarddnsserver import ForwardDNSServer
298+
299+ return ForwardDNSServer.objects.filter(domains=self)
300+
301 def add_delegations(self, mapping, ns_host_name, dns_ip_list, default_ttl):
302 """Find any subdomains that need to be added to this domain, and add
303 them.
304@@ -366,9 +382,17 @@ class Domain(CleanSave, TimestampedModel):
305 if self.name is not None and self.name.endswith("."):
306 self.name = self.name[:-1]
307
308+ def validate_authority(self):
309+ if self.authoritative and len(self.forward_dns_servers) > 0:
310+ raise ValidationError(
311+ "A Domain cannot be both authoritative and have"
312+ "forward DNS servers"
313+ )
314+
315 def clean(self, *args, **kwargs):
316 super().clean(*args, **kwargs)
317 self.clean_name()
318+ self.validate_authority()
319
320 def render_json_for_related_rrdata(
321 self, for_list=False, include_dnsdata=True, as_dict=False, user=None
322diff --git a/src/maasserver/models/forwarddnsserver.py b/src/maasserver/models/forwarddnsserver.py
323new file mode 100644
324index 0000000..9fe07be
325--- /dev/null
326+++ b/src/maasserver/models/forwarddnsserver.py
327@@ -0,0 +1,33 @@
328+""" Forward DNS Server Objects."""
329+
330+__all__ = [
331+ "ForwardDNSServerManager",
332+ "ForwardDNSServer",
333+]
334+
335+
336+from django.db.models import GenericIPAddressField, Manager, ManyToManyField
337+
338+from maasserver.models.cleansave import CleanSave
339+from maasserver.models.domain import Domain
340+from maasserver.models.timestampedmodel import TimestampedModel
341+
342+
343+class ForwardDNSServerManager(Manager):
344+ pass
345+
346+
347+# Due to migration 0155 calling Domain's manager directly, we cannot add forward dns servers as a column to a Domain
348+# so a separate table where one or more ForwardDNSServer(s) can be used in many Domains.
349+class ForwardDNSServer(CleanSave, TimestampedModel):
350+ """A `ForwardDNSServer`.
351+ :ivar ip_address: The IP address of the forward DNS server to forward queries to.
352+ :ivar domains: A many to many reference to domains that forward to this server."""
353+
354+ objects = ForwardDNSServerManager()
355+
356+ ip_address = GenericIPAddressField(
357+ null=False, default=None, editable=False, unique=True
358+ )
359+
360+ domains = ManyToManyField(Domain)
361diff --git a/src/maasserver/models/tests/test_domain.py b/src/maasserver/models/tests/test_domain.py
362index 840e792..356042d 100644
363--- a/src/maasserver/models/tests/test_domain.py
364+++ b/src/maasserver/models/tests/test_domain.py
365@@ -135,6 +135,15 @@ class TestDomainManager(MAASServerTestCase):
366 Domain.objects.filter_by_specifiers("name:%s" % name), [domain]
367 )
368
369+ def test_get_forward_domains(self):
370+ name1 = factory.make_name("domain")
371+ domain1 = factory.make_Domain(name=name1, authoritative=False)
372+ name2 = factory.make_name("other")
373+ factory.make_Domain(name=name2, authoritative=True)
374+ fwd_ip = factory.make_ip_address()
375+ factory.make_ForwardDNSServer(ip_address=fwd_ip, domains=[domain1])
376+ self.assertItemsEqual(Domain.objects.get_forward_domains(), [domain1])
377+
378
379 class DomainTest(MAASServerTestCase):
380 def test_creates_domain(self):
381@@ -181,6 +190,17 @@ class DomainTest(MAASServerTestCase):
382 domain.delete()
383 self.assertItemsEqual([], Domain.objects.filter(name=name))
384
385+ def test_validate_authority_raises_exception_when_both_authoritative_and_has_forward_dns_servers(
386+ self,
387+ ):
388+ name = factory.make_name("name")
389+ forward_server = factory.make_ip_address()
390+ domain = factory.make_Domain(name=name, authoritative=True)
391+ factory.make_ForwardDNSServer(
392+ ip_address=forward_server, domains=[domain]
393+ )
394+ self.assertRaises(ValidationError, domain.validate_authority)
395+
396 def test_cant_be_deleted_if_contains_resources(self):
397 domain = factory.make_Domain()
398 factory.make_DNSResource(domain=domain)
399diff --git a/src/maasserver/testing/factory.py b/src/maasserver/testing/factory.py
400index 929c159..bdefa86 100644
401--- a/src/maasserver/testing/factory.py
402+++ b/src/maasserver/testing/factory.py
403@@ -67,6 +67,7 @@ from maasserver.models import (
404 FileStorage,
405 Filesystem,
406 FilesystemGroup,
407+ ForwardDNSServer,
408 IPRange,
409 KeySource,
410 LargeFile,
411@@ -703,6 +704,15 @@ class Factory(maastesting.factory.Factory):
412 domain.save()
413 return domain
414
415+ def make_ForwardDNSServer(self, ip_address=None, domains=None):
416+ if ip_address is None:
417+ ip_address = self.make_ip_address()
418+ fwd_dns_srvr = ForwardDNSServer(ip_address=ip_address)
419+ fwd_dns_srvr.save()
420+ fwd_dns_srvr.domains.set(domains)
421+ fwd_dns_srvr.save()
422+ return fwd_dns_srvr
423+
424 def pick_rrset(self, rrtype=None, rrdata=None, exclude=[]):
425 while rrtype is None:
426 rrtype = self.pick_choice(
427diff --git a/src/provisioningserver/dns/actions.py b/src/provisioningserver/dns/actions.py
428index eabb3e8..7b8b1ca 100644
429--- a/src/provisioningserver/dns/actions.py
430+++ b/src/provisioningserver/dns/actions.py
431@@ -92,7 +92,7 @@ def bind_reload_zones(zone_list):
432 return ret
433
434
435-def bind_write_configuration(zones, trusted_networks):
436+def bind_write_configuration(zones, trusted_networks, forwarded_zones=None):
437 """Write BIND's configuration.
438
439 :param zones: Those zones to include in main config.
440@@ -107,7 +107,7 @@ def bind_write_configuration(zones, trusted_networks):
441 assert not isinstance(trusted_networks, (bytes, str))
442 assert isinstance(trusted_networks, Sequence)
443
444- dns_config = DNSConfig(zones=zones)
445+ dns_config = DNSConfig(zones=zones, forwarded_zones=forwarded_zones)
446 dns_config.write_config(trusted_networks=trusted_networks)
447
448
449diff --git a/src/provisioningserver/dns/config.py b/src/provisioningserver/dns/config.py
450index 904ae8d..fe4c5a2 100644
451--- a/src/provisioningserver/dns/config.py
452+++ b/src/provisioningserver/dns/config.py
453@@ -289,10 +289,13 @@ class DNSConfig:
454 template_file_name = "named.conf.template"
455 target_file_name = MAAS_NAMED_CONF_NAME
456
457- def __init__(self, zones=None):
458+ def __init__(self, zones=None, forwarded_zones=None):
459 if zones is None:
460 zones = ()
461+ if forwarded_zones is None:
462+ forwarded_zones = ()
463 self.zones = zones
464+ self.forwarded_zones = forwarded_zones
465
466 def write_config(self, overwrite=True, **kwargs):
467 """Write out this DNS config file.
468@@ -303,6 +306,7 @@ class DNSConfig:
469 trusted_networks = kwargs.pop("trusted_networks", "")
470 context = {
471 "zones": self.zones,
472+ "forwarded_zones": self.forwarded_zones,
473 "DNS_CONFIG_DIR": get_dns_config_dir(),
474 "named_rndc_conf_path": get_named_rndc_conf_path(),
475 "trusted_networks": trusted_networks,
476diff --git a/src/provisioningserver/dns/tests/test_config.py b/src/provisioningserver/dns/tests/test_config.py
477index 042df69..97fa15b 100644
478--- a/src/provisioningserver/dns/tests/test_config.py
479+++ b/src/provisioningserver/dns/tests/test_config.py
480@@ -63,7 +63,7 @@ from provisioningserver.dns.zoneconfig import (
481 DNSReverseZoneConfig,
482 )
483 from provisioningserver.utils import locate_config
484-from provisioningserver.utils.isc import read_isc_file
485+from provisioningserver.utils.isc import parse_isc_string, read_isc_file
486
487 NAMED_CONF_OPTIONS_CONTENTS = dedent(
488 """\
489@@ -547,6 +547,28 @@ class TestDNSConfig(MAASTestCase):
490 ),
491 )
492
493+ def test_write_config_with_forwarded_zones(self):
494+ name = factory.make_name("domain")
495+ ip = factory.make_ip_address()
496+ forwarded_zones = [(name, [ip])]
497+ target_dir = patch_dns_config_path(self)
498+ DNSConfig(forwarded_zones=forwarded_zones).write_config()
499+ config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)
500+ expected_content = dedent(
501+ f"""
502+ zone "{name}" {{
503+ type forward;
504+ forward only;
505+ forwarders {{
506+ {ip};
507+ }};
508+ }};
509+ """
510+ )
511+ config = read_isc_file(config_path)
512+ expected = parse_isc_string(expected_content)
513+ self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])
514+
515 def test_write_config_makes_config_world_readable(self):
516 target_dir = patch_dns_config_path(self)
517 DNSConfig().write_config()
518diff --git a/src/provisioningserver/templates/dns/named.conf.template b/src/provisioningserver/templates/dns/named.conf.template
519index 938d772..f0f9841 100644
520--- a/src/provisioningserver/templates/dns/named.conf.template
521+++ b/src/provisioningserver/templates/dns/named.conf.template
522@@ -1,6 +1,6 @@
523 include "{{named_rndc_conf_path}}";
524
525-# Zone declarations.
526+# Authoritative Zone declarations.
527 {{for zone in zones}}
528 {{for zoneinfo in zone.zone_info}}
529 zone "{{zoneinfo.zone_name}}" {
530@@ -10,6 +10,19 @@ zone "{{zoneinfo.zone_name}}" {
531 {{endfor}}
532 {{endfor}}
533
534+# Forwarded Zone declarations.
535+{{for forwarded_zone in forwarded_zones}}
536+zone "{{forwarded_zone[0]}}" {
537+ type forward;
538+ forward only;
539+ forwarders {
540+ {{for forward_server in forwarded_zone[1]}}
541+ {{forward_server}};
542+ {{endfor}}
543+ };
544+};
545+{{endfor}}
546+
547 # Access control for recursive queries. See named.conf.options.inside.maas
548 # for the directives used on this ACL.
549 acl "trusted" {

Subscribers

People subscribed via source and target branches