From eefeb1414e5a629b520aec3495defc4dd9245eff Mon Sep 17 00:00:00 2001 From: Tim Simmons Date: Mon, 23 Feb 2015 19:13:09 +0000 Subject: [PATCH] Agent configuration and BIND9 improvements * Make sure BIND9 rndc addzone/delzone compatiblity is maintained by stripping the trailing dot on zone names * Write out non-relativized zone files * Add a transfer-source option to the Agent for AXFRs * Add a query-destination option to the Agent BIND9 Backend * Remove the 1 second sleep, as the Pool Manager should be able to recover Change-Id: Ief7483d477affd17a13139f591dfe94be8285b74 --- designate/agent/__init__.py | 2 ++ designate/agent/handler.py | 7 ++-- designate/backend/agent_backend/impl_bind9.py | 25 +++++++------- designate/dnsutils.py | 4 +-- designate/tests/test_agent/test_handler.py | 33 +++++++++++++++++-- etc/designate/designate.conf.sample | 2 ++ 6 files changed, 53 insertions(+), 20 deletions(-) diff --git a/designate/agent/__init__.py b/designate/agent/__init__.py index a77db1091..2184c0ff2 100644 --- a/designate/agent/__init__.py +++ b/designate/agent/__init__.py @@ -36,6 +36,8 @@ OPTS = [ help='List of masters for the Agent, format ip:port'), cfg.StrOpt('backend-driver', default='bind9', help='The backend driver to use'), + cfg.StrOpt('transfer-source', default=None, + help='An IP address to be used to fetch zones transferred in'), ] cfg.CONF.register_opts(OPTS, group='service:agent') diff --git a/designate/agent/handler.py b/designate/agent/handler.py index ebb1e6630..2b6d8ba6c 100644 --- a/designate/agent/handler.py +++ b/designate/agent/handler.py @@ -56,6 +56,7 @@ class RequestHandler(object): {'masters': self.masters}) self.allow_notify = CONF['service:agent'].allow_notify + self.transfer_source = CONF['service:agent'].transfer_source backend_driver = cfg.CONF['service:agent'].backend_driver self.backend = agent_backend.get_backend(backend_driver, self) @@ -123,7 +124,8 @@ class RequestHandler(object): {'verb': "CREATE", 'name': domain_name, 'host': requester}) try: - zone = dnsutils.do_axfr(domain_name, self.masters) + zone = dnsutils.do_axfr(domain_name, self.masters, + source=self.transfer_source) self.backend.create_domain(zone) except Exception: response.set_rcode(dns.rcode.from_text("SERVFAIL")) @@ -173,7 +175,8 @@ class RequestHandler(object): # Check that the serial is < serial above try: - zone = dnsutils.do_axfr(domain_name, self.masters) + zone = dnsutils.do_axfr(domain_name, self.masters, + source=self.transfer_source) self.backend.update_domain(zone) except Exception: response.set_rcode(dns.rcode.from_text("SERVFAIL")) diff --git a/designate/backend/agent_backend/impl_bind9.py b/designate/backend/agent_backend/impl_bind9.py index 54a585231..51a7c3868 100644 --- a/designate/backend/agent_backend/impl_bind9.py +++ b/designate/backend/agent_backend/impl_bind9.py @@ -13,10 +13,10 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import time import os import dns +import dns.resolver from oslo.config import cfg from oslo.concurrency import lockutils from oslo_log import log as logging @@ -46,7 +46,9 @@ class Bind9Backend(base.AgentBackend): help='RNDC Config File'), cfg.StrOpt('rndc-key-file', default=None, help='RNDC Key File'), cfg.StrOpt('zone-file-path', default='$state_path/zones', - help='Path where zone files are stored') + help='Path where zone files are stored'), + cfg.StrOpt('query-destination', default='127.0.0.1', + help='Host to query when finding domains') ] return [(group, opts)] @@ -57,7 +59,7 @@ class Bind9Backend(base.AgentBackend): def find_domain_serial(self, domain_name): LOG.debug("Finding %s" % domain_name) resolver = dns.resolver.Resolver() - resolver.nameservers = ['127.0.0.1'] + resolver.nameservers = [cfg.CONF[CFG_GROUP].query_destination] try: rdata = resolver.query(domain_name, 'SOA')[0] except Exception: @@ -77,7 +79,7 @@ class Bind9Backend(base.AgentBackend): rndc_op = 'delzone' # RNDC doesn't like the trailing dot on the domain name - rndc_call = self._rndc_base() + [rndc_op, domain_name[:-1]] + rndc_call = self._rndc_base() + [rndc_op, domain_name.rstrip('.')] utils.execute(*rndc_call) @@ -101,7 +103,9 @@ class Bind9Backend(base.AgentBackend): def _sync_domain(self, domain, new_domain_flag=False): """Sync a single domain's zone file and reload bind config""" - domain_name = domain.origin.to_text() + # NOTE: Different versions of BIND9 behave differently with a trailing + # dot, so we're just going to take it off. + domain_name = domain.origin.to_text().rstrip('.') # NOTE: Only one thread should be working with the Zonefile at a given # time. The sleep(1) below introduces a not insignificant risk @@ -112,9 +116,9 @@ class Bind9Backend(base.AgentBackend): zone_path = cfg.CONF[CFG_GROUP].zone_file_path output_path = os.path.join(zone_path, - '%szone' % domain_name) + '%s.zone' % domain_name) - domain.to_file(output_path) + domain.to_file(output_path, relativize=False) rndc_call = self._rndc_base() @@ -130,13 +134,6 @@ class Bind9Backend(base.AgentBackend): rndc_call.extend([rndc_op]) rndc_call.extend([domain_name]) - if not new_domain_flag: - # NOTE: Bind9 will only ever attempt to re-read a zonefile if - # the file's timestamp has changed since the previous - # reload. A one second sleep ensures we cross over a - # second boundary before allowing the next change. - time.sleep(1) - LOG.debug('Calling RNDC with: %s' % " ".join(rndc_call)) self._execute_rndc(rndc_call) diff --git a/designate/dnsutils.py b/designate/dnsutils.py index 7c2813836..1d4552ef5 100644 --- a/designate/dnsutils.py +++ b/designate/dnsutils.py @@ -184,7 +184,7 @@ def bind_udp(host, port): return sock_udp -def do_axfr(zone_name, masters): +def do_axfr(zone_name, masters, source=None): """ Performs an AXFR for a given zone name """ @@ -195,7 +195,7 @@ def do_axfr(zone_name, masters): {'name': zone_name, 'host': master}) xfr = dns.query.xfr(master['ip'], zone_name, relativize=False, - port=master['port']) + port=master['port'], source=source) try: # TODO(Tim): Add a timeout to this function diff --git a/designate/tests/test_agent/test_handler.py b/designate/tests/test_agent/test_handler.py index 08ffc38b8..4dfca16d5 100644 --- a/designate/tests/test_agent/test_handler.py +++ b/designate/tests/test_agent/test_handler.py @@ -29,13 +29,14 @@ class AgentRequestHandlerTest(AgentTestCase): super(AgentRequestHandlerTest, self).setUp() self.config(allow_notify=["0.0.0.0"], backend_driver="fake", + transfer_source="1.2.3.4", group='service:agent') self.handler = handler.RequestHandler() self.addr = ["0.0.0.0", 5558] @mock.patch.object(dns.resolver.Resolver, 'query') @mock.patch('designate.dnsutils.do_axfr') - def test_receive_notify(self, func, axfrfunc): + def test_receive_notify(self, doaxfr, query): """ Get a NOTIFY and ensure the response is right, and an AXFR is triggered @@ -83,7 +84,7 @@ class AgentRequestHandlerTest(AgentTestCase): @mock.patch.object(dns.resolver.Resolver, 'query') @mock.patch('designate.dnsutils.do_axfr') - def test_receive_create(self, func, func2): + def test_receive_create(self, doaxfr, query): """ Get a CREATE and ensure the response is right, and an AXFR is triggered, and the proper backend @@ -180,3 +181,31 @@ class AgentRequestHandlerTest(AgentTestCase): response = self.handler(request).to_wire() self.assertEqual(expected_response, binascii.b2a_hex(response)) + + @mock.patch.object(dns.resolver.Resolver, 'query') + @mock.patch.object(designate.dnsutils, 'do_axfr') + def test_transfer_source(self, doaxfr, query): + """ + Get a CREATE and ensure the response is right, + and an AXFR is triggered with the right source IP + """ + payload = "735d70000001000000000000076578616d706c6503636f6d00ff02ff00" + # Expected NOERROR other fields are + # opcode 14 + # rcode NOERROR + # flags QR AA + # ;QUESTION + # example.com. CLASS65280 TYPE65282 + # ;ANSWER + # ;AUTHORITY + # ;ADDITIONAL + expected_response = ("735df4000001000000000000076578616d706c6503636f6d" + "00ff02ff00") + request = dns.message.from_wire(binascii.a2b_hex(payload)) + request.environ = {'addr': ["0.0.0.0", 1234]} + with mock.patch.object( + designate.backend.agent_backend.impl_fake.FakeBackend, + 'find_domain_serial', return_value=None): + response = self.handler(request).to_wire() + doaxfr.assert_called_with('example.com.', [], source="1.2.3.4") + self.assertEqual(expected_response, binascii.b2a_hex(response)) diff --git a/etc/designate/designate.conf.sample b/etc/designate/designate.conf.sample index 24bf8b2c1..91c179375 100644 --- a/etc/designate/designate.conf.sample +++ b/etc/designate/designate.conf.sample @@ -138,6 +138,7 @@ debug = False #allow_notify = 127.0.0.1 #masters = 127.0.0.1:5354 #backend_driver = fake +#transfer_source = None #----------------------- @@ -269,3 +270,4 @@ debug = False #rndc_config_file = /etc/rndc.conf #rndc_key_file = /etc/rndc.key #zone_file_path = $state_path/zones +#query_destination = 127.0.0.1