Merge ~cgrabowski/maas:backport_lp2056050_fix_to_3.5 into maas:3.5

Proposed by Christian Grabowski
Status: Merged
Approved by: Christian Grabowski
Approved revision: 11095eeeb3c8a9bd9686a3d548d28fd1712fa763
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:backport_lp2056050_fix_to_3.5
Merge into: maas:3.5
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
Christian Grabowski Approve
Review via email: mp+462238@code.launchpad.net

Commit message

fix: allow non-default ports for forward dns servers
(cherry picked from commit d21ba292d3f8f8bff68279ec2538b91bb831ceba)

To post a comment you must log in.
Revision history for this message
Christian Grabowski (cgrabowski) wrote :

self-approving backport

review: Approve

Update scan failed

At least one of the branches involved have failed to scan. You can manually schedule a rescan if required.

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 2515a5a..a7da4f4 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 b7b376c..2ef5cbf 100644
--- a/src/maasserver/dns/tests/test_config.py
+++ b/src/maasserver/dns/tests/test_config.py
@@ -95,7 +95,10 @@ class TestDNSUtilities(MAASServerTestCase):
95 )95 )
96 self.assertCountEqual(96 self.assertCountEqual(
97 fwd_zones,97 fwd_zones,
98 [(name1, [fwd_srvr1.ip_address]), (name2, [fwd_srvr2.ip_address])],98 [
99 (name1, [(fwd_srvr1.ip_address, fwd_srvr1.port)]),
100 (name2, [(fwd_srvr2.ip_address, fwd_srvr2.port)]),
101 ],
99 )102 )
100103
101104
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..5551756
--- /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", "0317_migrate_defaultresource_zone"),
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 f6f8e24..7af43e4 100644
--- a/src/maasserver/tests/test_fields.py
+++ b/src/maasserver/tests/test_fields.py
@@ -11,6 +11,7 @@ from psycopg2 import OperationalError
11from maasserver.fields import (11from maasserver.fields import (
12 HostListFormField,12 HostListFormField,
13 IPListFormField,13 IPListFormField,
14 IPPortListFormField,
14 LargeObjectField,15 LargeObjectField,
15 LargeObjectFile,16 LargeObjectFile,
16 MODEL_NAME_VALIDATOR,17 MODEL_NAME_VALIDATOR,
@@ -302,6 +303,54 @@ class TestIPListFormField(MAASTestCase):
302 )303 )
303304
304305
306class TestIPPortListFormField(MAASTestCase):
307 def test_accepts_ipv4_with_port(self):
308 ips = [factory.make_ip_address(ipv6=False) for _ in range(5)]
309 ip_ports = [f"{ip}:80" for ip in ips]
310 input = ",".join(ip_ports)
311 self.assertEqual(
312 [(ip, 80) for ip in ips], IPPortListFormField().clean(input)
313 )
314
315 def test_accepts_ipv4_without_port(self):
316 ips = [factory.make_ip_address(ipv6=False) for _ in range(5)]
317 input = ",".join(ips)
318 self.assertEqual(
319 [(ip, 80) for ip in ips],
320 IPPortListFormField(default_port=80).clean(input),
321 )
322
323 def test_accepts_ipv6_with_port(self):
324 ips = [factory.make_ip_address(ipv6=True) for _ in range(5)]
325 ip_ports = [f"[{ip}]:80" for ip in ips]
326 input = ",".join(ip_ports)
327 self.assertEqual(
328 [(ip, 80) for ip in ips], IPPortListFormField().clean(input)
329 )
330
331 def test_accepts_ipv6_without_port(self):
332 ips = [factory.make_ip_address(ipv6=True) for _ in range(5)]
333 input = ",".join(ips)
334 self.assertEqual(
335 [(ip, 80) for ip in ips],
336 IPPortListFormField(default_port=80).clean(input),
337 )
338
339 def test_rejects_invalid_ip(self):
340 ip_ports = [
341 f"{factory.make_ip_address(ipv6=False)}:80" for _ in range(4)
342 ]
343 invalid = f"{factory.make_name()}:80"
344 ip_ports.append(invalid)
345 input = ",".join(ip_ports)
346 error = self.assertRaises(
347 ValidationError, IPPortListFormField().clean, input
348 )
349 self.assertIn(
350 f"Invalid IP and port combination: {invalid}", error.message
351 )
352
353
305class TestHostListFormField(MAASTestCase):354class TestHostListFormField(MAASTestCase):
306 def test_accepts_none(self):355 def test_accepts_none(self):
307 self.assertIsNone(HostListFormField().clean(None))356 self.assertIsNone(HostListFormField().clean(None))
diff --git a/src/provisioningserver/dns/tests/test_config.py b/src/provisioningserver/dns/tests/test_config.py
index 94d6f37..6007b64 100644
--- a/src/provisioningserver/dns/tests/test_config.py
+++ b/src/provisioningserver/dns/tests/test_config.py
@@ -557,7 +557,7 @@ class TestDNSConfig(MAASTestCase):
557 def test_write_config_with_forwarded_zones(self):557 def test_write_config_with_forwarded_zones(self):
558 name = factory.make_name("domain")558 name = factory.make_name("domain")
559 ip = factory.make_ip_address()559 ip = factory.make_ip_address()
560 forwarded_zones = [(name, [ip])]560 forwarded_zones = [(name, [(ip, None)])]
561 target_dir = patch_dns_config_path(self)561 target_dir = patch_dns_config_path(self)
562 DNSConfig(forwarded_zones=forwarded_zones).write_config()562 DNSConfig(forwarded_zones=forwarded_zones).write_config()
563 config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)563 config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)
@@ -567,7 +567,7 @@ class TestDNSConfig(MAASTestCase):
567 type forward;567 type forward;
568 forward only;568 forward only;
569 forwarders {{569 forwarders {{
570 {ip};570 {ip} port 53;
571 }};571 }};
572 }};572 }};
573 """573 """
@@ -576,6 +576,30 @@ class TestDNSConfig(MAASTestCase):
576 expected = parse_isc_string(expected_content)576 expected = parse_isc_string(expected_content)
577 self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])577 self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])
578578
579 def test_write_config_with_forwarded_zones_with_custom_port(self):
580 name = factory.make_name("domain")
581 ip = factory.make_ip_address()
582 port = 5353
583 forwarded_zones = [(name, [(ip, port)])]
584 target_dir = patch_dns_config_path(self)
585 DNSConfig(forwarded_zones=forwarded_zones).write_config()
586 config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME)
587 expected_content = dedent(
588 f"""
589 zone "{name}" {{
590 type forward;
591 forward only;
592 forwarders {{
593 {ip} port {port};
594 }};
595 }};
596 """
597 )
598 config = read_isc_file(config_path)
599 print(config)
600 expected = parse_isc_string(expected_content)
601 self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])
602
579 def test_write_config_makes_config_world_readable(self):603 def test_write_config_makes_config_world_readable(self):
580 target_dir = patch_dns_config_path(self)604 target_dir = patch_dns_config_path(self)
581 DNSConfig().write_config()605 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