Merge ~cgrabowski/maas:backport_lp2056050_3.4 into maas:3.4

Proposed by Christian Grabowski
Status: Merged
Approved by: Björn Tillenius
Approved revision: 2813fddd11a7a573d793c0746cbc47565c5712b4
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:backport_lp2056050_3.4
Merge into: maas:3.4
Diff against target: 340 lines (+178/-11)
10 files modified
src/maasserver/dns/config.py (+1/-1)
src/maasserver/dns/tests/test_config.py (+4/-1)
src/maasserver/fields.py (+39/-0)
src/maasserver/forms/domain.py (+5/-5)
src/maasserver/forms/tests/test_domain.py (+24/-0)
src/maasserver/migrations/maasserver/0318_add_port_to_forward_dns_servers.py (+17/-0)
src/maasserver/models/forwarddnsserver.py (+12/-1)
src/maasserver/tests/test_fields.py (+49/-0)
src/provisioningserver/dns/tests/test_config.py (+26/-2)
src/provisioningserver/templates/dns/named.conf.template (+1/-1)
Reviewer Review Type Date Requested Status
Björn Tillenius Approve
MAAS Lander Approve
Review via email: mp+462712@code.launchpad.net

Commit message

fix: allow non-default ports for forward dns servers

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

UNIT TESTS
-b backport_lp2056050_3.4 lp:~cgrabowski/maas/+git/maas into -b 3.4 lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 2813fddd11a7a573d793c0746cbc47565c5712b4

review: Approve
Revision history for this message
Björn Tillenius (bjornt) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/dns/config.py b/src/maasserver/dns/config.py
index b03d47d..4709fe6 100644
--- a/src/maasserver/dns/config.py
+++ b/src/maasserver/dns/config.py
@@ -60,7 +60,7 @@ def forward_domains_to_forwarded_zones(forward_domains):
60 (60 (
61 domain.name,61 domain.name,
62 [62 [
63 fwd_dns_srvr.ip_address63 (fwd_dns_srvr.ip_address, fwd_dns_srvr.port)
64 for fwd_dns_srvr in domain.forward_dns_servers64 for fwd_dns_srvr in domain.forward_dns_servers
65 ],65 ],
66 )66 )
diff --git a/src/maasserver/dns/tests/test_config.py b/src/maasserver/dns/tests/test_config.py
index 44507a0..620cf44 100644
--- a/src/maasserver/dns/tests/test_config.py
+++ b/src/maasserver/dns/tests/test_config.py
@@ -102,7 +102,10 @@ class TestDNSUtilities(MAASServerTestCase):
102 )102 )
103 self.assertCountEqual(103 self.assertCountEqual(
104 fwd_zones,104 fwd_zones,
105 [(name1, [fwd_srvr1.ip_address]), (name2, [fwd_srvr2.ip_address])],105 [
106 (name1, [(fwd_srvr1.ip_address, fwd_srvr1.port)]),
107 (name2, [(fwd_srvr2.ip_address, fwd_srvr2.port)]),
108 ],
106 )109 )
107110
108111
diff --git a/src/maasserver/fields.py b/src/maasserver/fields.py
index 661a4d6..f4bd280 100644
--- a/src/maasserver/fields.py
+++ b/src/maasserver/fields.py
@@ -5,6 +5,7 @@
55
6from itertools import chain6from itertools import chain
7import re7import re
8import urllib
89
9from django import forms10from django import forms
10from django.core.exceptions import ObjectDoesNotExist, ValidationError11from django.core.exceptions import ObjectDoesNotExist, ValidationError
@@ -332,6 +333,44 @@ class IPListFormField(forms.CharField):
332 return " ".join(ips)333 return " ".join(ips)
333334
334335
336class IPPortListFormField(IPListFormField):
337 def __init__(self, default_port=None, *args, **kwargs):
338 super().__init__(*args, **kwargs)
339 self._default_port = default_port
340
341 def clean(self, value):
342 if value is None:
343 return None
344 else:
345 ip_ports = re.split(self.separators, value)
346 ip_ports = [
347 ip_port.strip() for ip_port in ip_ports if ip_ports != ""
348 ]
349 result = []
350 for ip_port in ip_ports:
351 if "." in ip_port or ("[" == ip_port[0] and "]:" in ip_port):
352 sock_addr = urllib.parse.urlsplit("//" + ip_port)
353 ip = sock_addr.hostname
354 port = (
355 int(sock_addr.port)
356 if sock_addr.port
357 else self._default_port
358 )
359 else:
360 ip = ip_port
361 port = self._default_port
362 try:
363 GenericIPAddressField().clean(ip, model_instance=None)
364 IntegerField().clean(port, model_instance=None)
365 except ValidationError:
366 raise ValidationError(
367 f"Invalid IP and port combination: {ip_port};"
368 f"please provide a list of space-separated IP addresses {'and port' if not self._default_port else ''}"
369 )
370 result.append((ip, port))
371 return result
372
373
335class HostListFormField(forms.CharField):374class HostListFormField(forms.CharField):
336 """Accepts a space/comma separated list of hostnames or IP addresses.375 """Accepts a space/comma separated list of hostnames or IP addresses.
337376
diff --git a/src/maasserver/forms/domain.py b/src/maasserver/forms/domain.py
index b530f3f..d5739a4 100644
--- a/src/maasserver/forms/domain.py
+++ b/src/maasserver/forms/domain.py
@@ -7,7 +7,7 @@
7from django import forms7from django import forms
8from django.core.exceptions import ValidationError8from django.core.exceptions import ValidationError
99
10from maasserver.fields import IPListFormField10from maasserver.fields import IPPortListFormField
11from maasserver.forms import APIEditMixin, MAASModelForm11from maasserver.forms import APIEditMixin, MAASModelForm
12from maasserver.models.domain import Domain12from maasserver.models.domain import Domain
13from maasserver.models.forwarddnsserver import ForwardDNSServer13from maasserver.models.forwarddnsserver import ForwardDNSServer
@@ -24,16 +24,16 @@ class DomainForm(MAASModelForm):
2424
25 authoritative = forms.NullBooleanField(required=False)25 authoritative = forms.NullBooleanField(required=False)
2626
27 forward_dns_servers = IPListFormField(required=False)27 forward_dns_servers = IPPortListFormField(default_port=53, required=False)
2828
29 def save(self):29 def save(self):
30 super(MAASModelForm, self).save()30 super(MAASModelForm, self).save()
31 fwd_srvrs = self.cleaned_data.get("forward_dns_servers")31 fwd_srvrs = self.cleaned_data.get("forward_dns_servers")
32 if fwd_srvrs is not None:32 if fwd_srvrs is not None:
33 fwd_srvrs_list = fwd_srvrs.split(" ")33 for fwd_srvr_ip, fwd_srvr_port in fwd_srvrs:
34 for fwd_srvr_ip in fwd_srvrs_list:
35 fwd_srvr = ForwardDNSServer.objects.get_or_create(34 fwd_srvr = ForwardDNSServer.objects.get_or_create(
36 ip_address=fwd_srvr_ip35 ip_address=fwd_srvr_ip,
36 port=fwd_srvr_port,
37 )[0]37 )[0]
38 fwd_srvr.domains.add(self.instance)38 fwd_srvr.domains.add(self.instance)
39 fwd_srvr.save()39 fwd_srvr.save()
diff --git a/src/maasserver/forms/tests/test_domain.py b/src/maasserver/forms/tests/test_domain.py
index 043dc47..e9a3a3e 100644
--- a/src/maasserver/forms/tests/test_domain.py
+++ b/src/maasserver/forms/tests/test_domain.py
@@ -104,6 +104,30 @@ class TestDomainForm(MAASServerTestCase):
104 for fwd_dns_srvr in domain.forward_dns_servers104 for fwd_dns_srvr in domain.forward_dns_servers
105 ],105 ],
106 )106 )
107 for fwd_dns_srvr in domain.forward_dns_servers:
108 self.assertEqual(fwd_dns_srvr.port, 53)
109
110 def test_can_create_forward_dns_server_with_port(self):
111 name = factory.make_name("domain")
112 forward_dns_servers = [
113 f"{factory.make_ip_address(ipv6=False)}:5353" for _ in range(0, 2)
114 ]
115 form = DomainForm(
116 {
117 "name": name,
118 "authoritative": False,
119 "forward_dns_servers": " ".join(forward_dns_servers),
120 }
121 )
122 self.assertTrue(form.is_valid(), form.errors)
123 domain = form.save()
124 self.assertEqual(
125 forward_dns_servers,
126 [
127 fwd_dns_srvr.ip_and_port
128 for fwd_dns_srvr in domain.forward_dns_servers
129 ],
130 )
107131
108 def test_validate_authority(self):132 def test_validate_authority(self):
109 name = factory.make_name("domain")133 name = factory.make_name("domain")
diff --git a/src/maasserver/migrations/maasserver/0318_add_port_to_forward_dns_servers.py b/src/maasserver/migrations/maasserver/0318_add_port_to_forward_dns_servers.py
110new file mode 100644134new file mode 100644
index 0000000..322927f
--- /dev/null
+++ b/src/maasserver/migrations/maasserver/0318_add_port_to_forward_dns_servers.py
@@ -0,0 +1,17 @@
1# Generated by Django 3.2.12 on 2024-03-07 17:16
2
3from django.db import migrations, models
4
5
6class Migration(migrations.Migration):
7 dependencies = [
8 ("maasserver", "0304_interface_params_no_autoconf"),
9 ]
10
11 operations = [
12 migrations.AddField(
13 model_name="forwarddnsserver",
14 name="port",
15 field=models.IntegerField(default=53),
16 ),
17 ]
diff --git a/src/maasserver/models/forwarddnsserver.py b/src/maasserver/models/forwarddnsserver.py
index 5dcf740..64eb2e8 100644
--- a/src/maasserver/models/forwarddnsserver.py
+++ b/src/maasserver/models/forwarddnsserver.py
@@ -6,7 +6,12 @@ __all__ = [
6]6]
77
88
9from django.db.models import GenericIPAddressField, Manager, ManyToManyField9from django.db.models import (
10 GenericIPAddressField,
11 IntegerField,
12 Manager,
13 ManyToManyField,
14)
1015
11from maasserver.models.cleansave import CleanSave16from maasserver.models.cleansave import CleanSave
12from maasserver.models.domain import Domain17from maasserver.models.domain import Domain
@@ -31,4 +36,10 @@ class ForwardDNSServer(CleanSave, TimestampedModel):
31 null=False, default=None, editable=False, unique=True36 null=False, default=None, editable=False, unique=True
32 )37 )
3338
39 port = IntegerField(null=False, default=53)
40
34 domains = ManyToManyField(Domain)41 domains = ManyToManyField(Domain)
42
43 @property
44 def ip_and_port(self):
45 return f"{self.ip_address}:{self.port}"
diff --git a/src/maasserver/tests/test_fields.py b/src/maasserver/tests/test_fields.py
index 643366f..7af6607 100644
--- a/src/maasserver/tests/test_fields.py
+++ b/src/maasserver/tests/test_fields.py
@@ -12,6 +12,7 @@ from testtools import ExpectedException
12from maasserver.fields import (12from maasserver.fields import (
13 HostListFormField,13 HostListFormField,
14 IPListFormField,14 IPListFormField,
15 IPPortListFormField,
15 LargeObjectField,16 LargeObjectField,
16 LargeObjectFile,17 LargeObjectFile,
17 MODEL_NAME_VALIDATOR,18 MODEL_NAME_VALIDATOR,
@@ -304,6 +305,54 @@ class TestIPListFormField(MAASTestCase):
304 )305 )
305306
306307
308class TestIPPortListFormField(MAASTestCase):
309 def test_accepts_ipv4_with_port(self):
310 ips = [factory.make_ip_address(ipv6=False) for _ in range(5)]
311 ip_ports = [f"{ip}:80" for ip in ips]
312 input = ",".join(ip_ports)
313 self.assertEqual(
314 [(ip, 80) for ip in ips], IPPortListFormField().clean(input)
315 )
316
317 def test_accepts_ipv4_without_port(self):
318 ips = [factory.make_ip_address(ipv6=False) for _ in range(5)]
319 input = ",".join(ips)
320 self.assertEqual(
321 [(ip, 80) for ip in ips],
322 IPPortListFormField(default_port=80).clean(input),
323 )
324
325 def test_accepts_ipv6_with_port(self):
326 ips = [factory.make_ip_address(ipv6=True) for _ in range(5)]
327 ip_ports = [f"[{ip}]:80" for ip in ips]
328 input = ",".join(ip_ports)
329 self.assertEqual(
330 [(ip, 80) for ip in ips], IPPortListFormField().clean(input)
331 )
332
333 def test_accepts_ipv6_without_port(self):
334 ips = [factory.make_ip_address(ipv6=True) for _ in range(5)]
335 input = ",".join(ips)
336 self.assertEqual(
337 [(ip, 80) for ip in ips],
338 IPPortListFormField(default_port=80).clean(input),
339 )
340
341 def test_rejects_invalid_ip(self):
342 ip_ports = [
343 f"{factory.make_ip_address(ipv6=False)}:80" for _ in range(4)
344 ]
345 invalid = f"{factory.make_name()}:80"
346 ip_ports.append(invalid)
347 input = ",".join(ip_ports)
348 error = self.assertRaises(
349 ValidationError, IPPortListFormField().clean, input
350 )
351 self.assertIn(
352 f"Invalid IP and port combination: {invalid}", error.message
353 )
354
355
307class TestHostListFormField(MAASTestCase):356class TestHostListFormField(MAASTestCase):
308 def test_accepts_none(self):357 def test_accepts_none(self):
309 self.assertIsNone(HostListFormField().clean(None))358 self.assertIsNone(HostListFormField().clean(None))
diff --git a/src/provisioningserver/dns/tests/test_config.py b/src/provisioningserver/dns/tests/test_config.py
index 8c57ce4..9c7ad2f 100644
--- a/src/provisioningserver/dns/tests/test_config.py
+++ b/src/provisioningserver/dns/tests/test_config.py
@@ -601,7 +601,7 @@ class TestDNSConfig(MAASTestCase):
601 def test_write_config_with_forwarded_zones(self):601 def test_write_config_with_forwarded_zones(self):
602 name = factory.make_name("domain")602 name = factory.make_name("domain")
603 ip = factory.make_ip_address()603 ip = factory.make_ip_address()
604 forwarded_zones = [(name, [ip])]604 forwarded_zones = [(name, [(ip, None)])]
605 target_dir = patch_dns_config_path(self)605 target_dir = patch_dns_config_path(self)
606 DNSConfig(forwarded_zones=forwarded_zones).write_config()606 DNSConfig(forwarded_zones=forwarded_zones).write_config()
607 config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)607 config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)
@@ -611,7 +611,7 @@ class TestDNSConfig(MAASTestCase):
611 type forward;611 type forward;
612 forward only;612 forward only;
613 forwarders {{613 forwarders {{
614 {ip};614 {ip} port 53;
615 }};615 }};
616 }};616 }};
617 """617 """
@@ -620,6 +620,30 @@ class TestDNSConfig(MAASTestCase):
620 expected = parse_isc_string(expected_content)620 expected = parse_isc_string(expected_content)
621 self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])621 self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])
622622
623 def test_write_config_with_forwarded_zones_with_custom_port(self):
624 name = factory.make_name("domain")
625 ip = factory.make_ip_address()
626 port = 5353
627 forwarded_zones = [(name, [(ip, port)])]
628 target_dir = patch_dns_config_path(self)
629 DNSConfig(forwarded_zones=forwarded_zones).write_config()
630 config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)
631 expected_content = dedent(
632 f"""
633 zone "{name}" {{
634 type forward;
635 forward only;
636 forwarders {{
637 {ip} port {port};
638 }};
639 }};
640 """
641 )
642 config = read_isc_file(config_path)
643 print(config)
644 expected = parse_isc_string(expected_content)
645 self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])
646
623 def test_write_config_makes_config_world_readable(self):647 def test_write_config_makes_config_world_readable(self):
624 target_dir = patch_dns_config_path(self)648 target_dir = patch_dns_config_path(self)
625 DNSConfig().write_config()649 DNSConfig().write_config()
diff --git a/src/provisioningserver/templates/dns/named.conf.template b/src/provisioningserver/templates/dns/named.conf.template
index 7217ca8..a909560 100644
--- a/src/provisioningserver/templates/dns/named.conf.template
+++ b/src/provisioningserver/templates/dns/named.conf.template
@@ -21,7 +21,7 @@ zone "{{forwarded_zone[0]}}" {
21 forward only;21 forward only;
22 forwarders {22 forwarders {
23 {{for forward_server in forwarded_zone[1]}}23 {{for forward_server in forwarded_zone[1]}}
24 {{forward_server}};24 {{forward_server[0]}} port {{ forward_server[1] if forward_server[1] else 53 }};
25 {{endfor}}25 {{endfor}}
26 };26 };
27};27};

Subscribers

People subscribed via source and target branches