Merge ~cgrabowski/maas:backport_glue_zone_fix into maas:3.3

Proposed by Christian Grabowski
Status: Merged
Approved by: Christian Grabowski
Approved revision: 7aa2fa996039e7f286717acfeeb107129205c18a
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~cgrabowski/maas:backport_glue_zone_fix
Merge into: maas:3.3
Diff against target: 320 lines (+150/-38)
7 files modified
src/maasserver/dns/tests/test_zonegenerator.py (+10/-1)
src/maasserver/dns/zonegenerator.py (+8/-3)
src/provisioningserver/dns/actions.py (+42/-0)
src/provisioningserver/dns/config.py (+14/-14)
src/provisioningserver/dns/tests/test_actions.py (+26/-3)
src/provisioningserver/dns/tests/test_config.py (+22/-0)
src/provisioningserver/dns/zoneconfig.py (+28/-17)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Christian Grabowski Approve
Review via email: mp+436080@code.launchpad.net

Commit message

use rndc freeze and thaw to allow BIND to handle receiving full reloads and dynamic updates more rapidly
ensure updates are only for the correct subnet
(cherry picked from commit 0eb397ff84eb81f38a4a3dedae81855de55da32e)

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

self-approving backport

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

UNIT TESTS
-b backport_glue_zone_fix lp:~cgrabowski/maas/+git/maas into -b 3.3 lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 7aa2fa996039e7f286717acfeeb107129205c18a

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/tests/test_zonegenerator.py b/src/maasserver/dns/tests/test_zonegenerator.py
index 598074f..ee649a4 100644
--- a/src/maasserver/dns/tests/test_zonegenerator.py
+++ b/src/maasserver/dns/tests/test_zonegenerator.py
@@ -514,7 +514,9 @@ class TestZoneGenerator(MAASServerTestCase):
514 def test_glue_receives_correct_dynamic_updates(self):514 def test_glue_receives_correct_dynamic_updates(self):
515 domain = factory.make_Domain()515 domain = factory.make_Domain()
516 subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr))516 subnet = factory.make_Subnet(cidr=str(IPNetwork("10/29").cidr))
517 other_subnet = factory.make_Subnet()
517 sip = factory.make_StaticIPAddress(subnet=subnet)518 sip = factory.make_StaticIPAddress(subnet=subnet)
519 other_sip = factory.make_StaticIPAddress(subnet=other_subnet)
518 factory.make_Node_with_Interface_on_Subnet(520 factory.make_Node_with_Interface_on_Subnet(
519 subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric521 subnet=subnet, vlan=subnet.vlan, fabric=subnet.vlan.fabric
520 )522 )
@@ -528,7 +530,14 @@ class TestZoneGenerator(MAASServerTestCase):
528 zone=domain.name,530 zone=domain.name,
529 rectype="A",531 rectype="A",
530 answer=sip.ip,532 answer=sip.ip,
531 )533 ),
534 DynamicDNSUpdate(
535 operation="INSERT",
536 name=update_rec.name,
537 zone=domain.name,
538 rectype="A",
539 answer=other_sip.ip,
540 ),
532 ]541 ]
533 zones = ZoneGenerator(542 zones = ZoneGenerator(
534 domain,543 domain,
diff --git a/src/maasserver/dns/zonegenerator.py b/src/maasserver/dns/zonegenerator.py
index 658147f..2a87990 100644
--- a/src/maasserver/dns/zonegenerator.py
+++ b/src/maasserver/dns/zonegenerator.py
@@ -458,7 +458,7 @@ class ZoneGenerator:
458 for update in dynamic_updates458 for update in dynamic_updates
459 if update.answer459 if update.answer
460 and update.answer_is_ip460 and update.answer_is_ip
461 and (IPAddress(update.answer) in IPNetwork(subnet.cidr))461 and (update.answer_as_ip in IPNetwork(subnet.cidr))
462 ]462 ]
463463
464 yield DNSReverseZoneConfig(464 yield DNSReverseZoneConfig(
@@ -490,11 +490,16 @@ class ZoneGenerator:
490 if (490 if (
491 update.answer491 update.answer
492 and update.answer_is_ip492 and update.answer_is_ip
493 and IPAddress(update.answer) in exclude_net493 and update.answer_as_ip in exclude_net
494 ):494 ):
495 glue_update = False495 glue_update = False
496 break496 break
497 if glue_update:497 if (
498 glue_update
499 and update.answer
500 and update.answer_is_ip
501 and update.answer_as_ip in network
502 ):
498 domain_updates.append(503 domain_updates.append(
499 DynamicDNSUpdate.as_reverse_record_update(504 DynamicDNSUpdate.as_reverse_record_update(
500 update, str(network)505 update, str(network)
diff --git a/src/provisioningserver/dns/actions.py b/src/provisioningserver/dns/actions.py
index bafbac9..7749ff5 100644
--- a/src/provisioningserver/dns/actions.py
+++ b/src/provisioningserver/dns/actions.py
@@ -42,6 +42,48 @@ def bind_reconfigure():
42 raise42 raise
4343
4444
45def bind_freeze_zone(zone=None, timeout=2):
46 cmd = ("freeze",) # freeze all zones
47 if zone:
48 cmd = ("freeze", zone) # freeze one zone
49
50 try:
51 execute_rndc_command(cmd, timeout=timeout)
52 except CalledProcessError as e:
53 maaslog.error(
54 f"Freezing {zone if zone else 'all zones'} for update failed"
55 )
56 ExternalProcessError.upgrade(e)
57 raise
58 except TimeoutExpired as e:
59 maaslog.error(
60 f"Freezing {zone if zone else 'all zones'} for update timed out"
61 )
62 ExternalProcessError.upgrade(e)
63 raise
64
65
66def bind_thaw_zone(zone=None, timeout=2):
67 cmd = ("thaw",) # thaw all zones
68 if zone:
69 cmd = ("thaw", zone) # thaw one zone
70
71 try:
72 execute_rndc_command(cmd, timeout=timeout)
73 except CalledProcessError as e:
74 maaslog.error(
75 f"Thawing {zone if zone else 'all zones'} for update failed"
76 )
77 ExternalProcessError.upgrade(e)
78 raise
79 except TimeoutExpired as e:
80 maaslog.error(
81 f"Thawing {zone if zone else 'all zones'} for update timed out"
82 )
83 ExternalProcessError.upgrade(e)
84 raise
85
86
45def bind_reload(timeout=2):87def bind_reload(timeout=2):
46 """Ask BIND to reload its configuration and all zone files. This operation88 """Ask BIND to reload its configuration and all zone files. This operation
47 is 'best effort' (with logging) as the server may not be running, and there89 is 'best effort' (with logging) as the server may not be running, and there
diff --git a/src/provisioningserver/dns/config.py b/src/provisioningserver/dns/config.py
index ef5a166..729b570 100644
--- a/src/provisioningserver/dns/config.py
+++ b/src/provisioningserver/dns/config.py
@@ -9,6 +9,7 @@ from contextlib import contextmanager
9from dataclasses import dataclass9from dataclasses import dataclass
10from datetime import datetime10from datetime import datetime
11import errno11import errno
12from functools import cached_property
12import grp13import grp
13import os14import os
14import os.path15import os.path
@@ -48,25 +49,17 @@ class DynamicDNSUpdate:
48 answer: Optional[str] = None49 answer: Optional[str] = None
4950
50 @classmethod51 @classmethod
51 def _is_ip(cls, answer):
52 if not answer:
53 return False
54 try:
55 IPAddress(answer)
56 except AddrFormatError:
57 return False
58 else:
59 return True
60
61 @classmethod
62 def create_from_trigger(cls, **kwargs):52 def create_from_trigger(cls, **kwargs):
63 answer = kwargs.get("answer")53 answer = kwargs.get("answer")
64 rectype = kwargs.pop("rectype")54 rectype = kwargs.pop("rectype")
65 if answer:55 if answer:
66 del kwargs["answer"]56 del kwargs["answer"]
67 # the DB trigger is unable to figure out if an IP is v6, so we do it here instead57 # the DB trigger is unable to figure out if an IP is v6, so we do it here instead
68 if cls._is_ip(answer):58 try:
69 ip = IPAddress(answer)59 ip = IPAddress(answer)
60 except AddrFormatError:
61 pass
62 else:
70 if ip.version == 6:63 if ip.version == 6:
71 rectype = "AAAA"64 rectype = "AAAA"
72 return cls(answer=answer, rectype=rectype, **kwargs)65 return cls(answer=answer, rectype=rectype, **kwargs)
@@ -86,9 +79,16 @@ class DynamicDNSUpdate:
86 rectype="PTR",79 rectype="PTR",
87 )80 )
8881
89 @property82 @cached_property
83 def answer_as_ip(self):
84 try:
85 return IPAddress(self.answer)
86 except AddrFormatError:
87 return None
88
89 @cached_property
90 def answer_is_ip(self):90 def answer_is_ip(self):
91 return DynamicDNSUpdate._is_ip(self.answer)91 return self.answer_as_ip is not None
9292
9393
94def get_dns_config_dir():94def get_dns_config_dir():
diff --git a/src/provisioningserver/dns/tests/test_actions.py b/src/provisioningserver/dns/tests/test_actions.py
index 1087bc0..7920448 100644
--- a/src/provisioningserver/dns/tests/test_actions.py
+++ b/src/provisioningserver/dns/tests/test_actions.py
@@ -68,15 +68,38 @@ class TestReconfigure(MAASTestCase):
68 self.assertRaises(ExternalProcessError, actions.bind_reconfigure)68 self.assertRaises(ExternalProcessError, actions.bind_reconfigure)
6969
7070
71class TestFreezeZone(MAASTestCase):
72 """Tests for :py:func:`actions.bind_freeze_zone`."""
73
74 def test_executes_rndc_command(self):
75 self.patch_autospec(actions, "execute_rndc_command")
76 zone = factory.make_name()
77 actions.bind_freeze_zone(zone=zone)
78 actions.execute_rndc_command.assert_called_once_with(
79 ("freeze", zone), timeout=2
80 )
81
82
83class TestThawZone(MAASTestCase):
84 """Tests for :py:func:`actions.bind_freeze_zone`."""
85
86 def test_executes_rndc_command(self):
87 self.patch_autospec(actions, "execute_rndc_command")
88 zone = factory.make_name()
89 actions.bind_thaw_zone(zone=zone)
90 actions.execute_rndc_command.assert_called_once_with(
91 ("thaw", zone), timeout=2
92 )
93
94
71class TestReload(MAASTestCase):95class TestReload(MAASTestCase):
72 """Tests for :py:func:`actions.bind_reload`."""96 """Tests for :py:func:`actions.bind_reload`."""
7397
74 def test_executes_rndc_command(self):98 def test_executes_rndc_command(self):
75 self.patch_autospec(actions, "execute_rndc_command")99 self.patch_autospec(actions, "execute_rndc_command")
76 actions.bind_reload()100 actions.bind_reload()
77 self.assertThat(101 actions.execute_rndc_command.assert_called_once_with(
78 actions.execute_rndc_command,102 ("reload",), timeout=2
79 MockCalledOnceWith(("reload",), timeout=2),
80 )103 )
81104
82 def test_logs_subprocess_error(self):105 def test_logs_subprocess_error(self):
diff --git a/src/provisioningserver/dns/tests/test_config.py b/src/provisioningserver/dns/tests/test_config.py
index 60b4c58..41a1bc5 100644
--- a/src/provisioningserver/dns/tests/test_config.py
+++ b/src/provisioningserver/dns/tests/test_config.py
@@ -666,6 +666,28 @@ class TestDynamicDNSUpdate(MAASTestCase):
666 )666 )
667 self.assertEqual(update.rectype, "AAAA")667 self.assertEqual(update.rectype, "AAAA")
668668
669 def test_answer_as_ip_returns_ip_when_answer_is_an_ip(self):
670 domain = factory.make_name()
671 update = DynamicDNSUpdate(
672 operation="INSERT",
673 zone=domain,
674 name=f"{factory.make_name()}.{domain}",
675 rectype="A",
676 answer=factory.make_ip_address(),
677 )
678 self.assertIsNotNone(update.answer_as_ip)
679
680 def test_answer_as_ip_returns_none_when_answer_is_not_an_ip(self):
681 domain = factory.make_name()
682 update = DynamicDNSUpdate(
683 operation="INSERT",
684 zone=domain,
685 name=f"{factory.make_name()}.{domain}",
686 rectype="CNAME",
687 answer=factory.make_name(),
688 )
689 self.assertIsNone(update.answer_as_ip)
690
669 def test_answer_is_ip_returns_true_when_answer_is_an_ip(self):691 def test_answer_is_ip_returns_true_when_answer_is_an_ip(self):
670 domain = factory.make_name()692 domain = factory.make_name()
671 update = DynamicDNSUpdate(693 update = DynamicDNSUpdate(
diff --git a/src/provisioningserver/dns/zoneconfig.py b/src/provisioningserver/dns/zoneconfig.py
index 8dec9b6..59e714b 100644
--- a/src/provisioningserver/dns/zoneconfig.py
+++ b/src/provisioningserver/dns/zoneconfig.py
@@ -12,7 +12,11 @@ from pathlib import Path
12from netaddr import IPAddress, IPNetwork, spanning_cidr12from netaddr import IPAddress, IPNetwork, spanning_cidr
13from netaddr.core import AddrFormatError13from netaddr.core import AddrFormatError
1414
15from provisioningserver.dns.actions import NSUpdateCommand15from provisioningserver.dns.actions import (
16 bind_freeze_zone,
17 bind_thaw_zone,
18 NSUpdateCommand,
19)
16from provisioningserver.dns.config import (20from provisioningserver.dns.config import (
17 compose_zone_file_config_path,21 compose_zone_file_config_path,
18 render_dns_template,22 render_dns_template,
@@ -333,24 +337,31 @@ class DNSForwardZoneConfig(DomainConfigBase):
333 else:337 else:
334 Path(f"{zi.target_path}.jnl").unlink(missing_ok=True)338 Path(f"{zi.target_path}.jnl").unlink(missing_ok=True)
335 self.requires_reload = True339 self.requires_reload = True
336 self.write_zone_file(340 needs_freeze_thaw = self.zone_file_exists(zi)
337 zi.target_path,341 if needs_freeze_thaw:
338 self.make_parameters(),342 bind_freeze_zone(zone=self.domain)
339 {343 try:
340 "mappings": {344 self.write_zone_file(
341 "A": self.get_A_mapping(345 zi.target_path,
342 self._mapping, self._ipv4_ttl346 self.make_parameters(),
343 ),347 {
344 "AAAA": self.get_AAAA_mapping(348 "mappings": {
345 self._mapping, self._ipv6_ttl349 "A": self.get_A_mapping(
350 self._mapping, self._ipv4_ttl
351 ),
352 "AAAA": self.get_AAAA_mapping(
353 self._mapping, self._ipv6_ttl
354 ),
355 },
356 "other_mapping": enumerate_rrset_mapping(
357 self._other_mapping
346 ),358 ),
359 "generate_directives": {"A": generate_directives},
347 },360 },
348 "other_mapping": enumerate_rrset_mapping(361 )
349 self._other_mapping362 finally:
350 ),363 if needs_freeze_thaw:
351 "generate_directives": {"A": generate_directives},364 bind_thaw_zone(zone=self.domain)
352 },
353 )
354365
355366
356class DNSReverseZoneConfig(DomainConfigBase):367class DNSReverseZoneConfig(DomainConfigBase):

Subscribers

People subscribed via source and target branches