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
This commit is contained in:
Tim Simmons 2015-02-23 19:13:09 +00:00
parent 1924d8e7e9
commit eefeb1414e
6 changed files with 53 additions and 20 deletions

View File

@ -36,6 +36,8 @@ OPTS = [
help='List of masters for the Agent, format ip:port'), help='List of masters for the Agent, format ip:port'),
cfg.StrOpt('backend-driver', default='bind9', cfg.StrOpt('backend-driver', default='bind9',
help='The backend driver to use'), 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') cfg.CONF.register_opts(OPTS, group='service:agent')

View File

@ -56,6 +56,7 @@ class RequestHandler(object):
{'masters': self.masters}) {'masters': self.masters})
self.allow_notify = CONF['service:agent'].allow_notify self.allow_notify = CONF['service:agent'].allow_notify
self.transfer_source = CONF['service:agent'].transfer_source
backend_driver = cfg.CONF['service:agent'].backend_driver backend_driver = cfg.CONF['service:agent'].backend_driver
self.backend = agent_backend.get_backend(backend_driver, self) self.backend = agent_backend.get_backend(backend_driver, self)
@ -123,7 +124,8 @@ class RequestHandler(object):
{'verb': "CREATE", 'name': domain_name, 'host': requester}) {'verb': "CREATE", 'name': domain_name, 'host': requester})
try: 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) self.backend.create_domain(zone)
except Exception: except Exception:
response.set_rcode(dns.rcode.from_text("SERVFAIL")) response.set_rcode(dns.rcode.from_text("SERVFAIL"))
@ -173,7 +175,8 @@ class RequestHandler(object):
# Check that the serial is < serial above # Check that the serial is < serial above
try: 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) self.backend.update_domain(zone)
except Exception: except Exception:
response.set_rcode(dns.rcode.from_text("SERVFAIL")) response.set_rcode(dns.rcode.from_text("SERVFAIL"))

View File

@ -13,10 +13,10 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import time
import os import os
import dns import dns
import dns.resolver
from oslo.config import cfg from oslo.config import cfg
from oslo.concurrency import lockutils from oslo.concurrency import lockutils
from oslo_log import log as logging from oslo_log import log as logging
@ -46,7 +46,9 @@ class Bind9Backend(base.AgentBackend):
help='RNDC Config File'), help='RNDC Config File'),
cfg.StrOpt('rndc-key-file', default=None, help='RNDC Key File'), cfg.StrOpt('rndc-key-file', default=None, help='RNDC Key File'),
cfg.StrOpt('zone-file-path', default='$state_path/zones', 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)] return [(group, opts)]
@ -57,7 +59,7 @@ class Bind9Backend(base.AgentBackend):
def find_domain_serial(self, domain_name): def find_domain_serial(self, domain_name):
LOG.debug("Finding %s" % domain_name) LOG.debug("Finding %s" % domain_name)
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
resolver.nameservers = ['127.0.0.1'] resolver.nameservers = [cfg.CONF[CFG_GROUP].query_destination]
try: try:
rdata = resolver.query(domain_name, 'SOA')[0] rdata = resolver.query(domain_name, 'SOA')[0]
except Exception: except Exception:
@ -77,7 +79,7 @@ class Bind9Backend(base.AgentBackend):
rndc_op = 'delzone' rndc_op = 'delzone'
# RNDC doesn't like the trailing dot on the domain name # 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) utils.execute(*rndc_call)
@ -101,7 +103,9 @@ class Bind9Backend(base.AgentBackend):
def _sync_domain(self, domain, new_domain_flag=False): def _sync_domain(self, domain, new_domain_flag=False):
"""Sync a single domain's zone file and reload bind config""" """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 # NOTE: Only one thread should be working with the Zonefile at a given
# time. The sleep(1) below introduces a not insignificant risk # 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 zone_path = cfg.CONF[CFG_GROUP].zone_file_path
output_path = os.path.join(zone_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() rndc_call = self._rndc_base()
@ -130,13 +134,6 @@ class Bind9Backend(base.AgentBackend):
rndc_call.extend([rndc_op]) rndc_call.extend([rndc_op])
rndc_call.extend([domain_name]) 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)) LOG.debug('Calling RNDC with: %s' % " ".join(rndc_call))
self._execute_rndc(rndc_call) self._execute_rndc(rndc_call)

View File

@ -184,7 +184,7 @@ def bind_udp(host, port):
return sock_udp 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 Performs an AXFR for a given zone name
""" """
@ -195,7 +195,7 @@ def do_axfr(zone_name, masters):
{'name': zone_name, 'host': master}) {'name': zone_name, 'host': master})
xfr = dns.query.xfr(master['ip'], zone_name, relativize=False, xfr = dns.query.xfr(master['ip'], zone_name, relativize=False,
port=master['port']) port=master['port'], source=source)
try: try:
# TODO(Tim): Add a timeout to this function # TODO(Tim): Add a timeout to this function

View File

@ -29,13 +29,14 @@ class AgentRequestHandlerTest(AgentTestCase):
super(AgentRequestHandlerTest, self).setUp() super(AgentRequestHandlerTest, self).setUp()
self.config(allow_notify=["0.0.0.0"], self.config(allow_notify=["0.0.0.0"],
backend_driver="fake", backend_driver="fake",
transfer_source="1.2.3.4",
group='service:agent') group='service:agent')
self.handler = handler.RequestHandler() self.handler = handler.RequestHandler()
self.addr = ["0.0.0.0", 5558] self.addr = ["0.0.0.0", 5558]
@mock.patch.object(dns.resolver.Resolver, 'query') @mock.patch.object(dns.resolver.Resolver, 'query')
@mock.patch('designate.dnsutils.do_axfr') @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, Get a NOTIFY and ensure the response is right,
and an AXFR is triggered and an AXFR is triggered
@ -83,7 +84,7 @@ class AgentRequestHandlerTest(AgentTestCase):
@mock.patch.object(dns.resolver.Resolver, 'query') @mock.patch.object(dns.resolver.Resolver, 'query')
@mock.patch('designate.dnsutils.do_axfr') @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, Get a CREATE and ensure the response is right,
and an AXFR is triggered, and the proper backend and an AXFR is triggered, and the proper backend
@ -180,3 +181,31 @@ class AgentRequestHandlerTest(AgentTestCase):
response = self.handler(request).to_wire() response = self.handler(request).to_wire()
self.assertEqual(expected_response, binascii.b2a_hex(response)) 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))

View File

@ -138,6 +138,7 @@ debug = False
#allow_notify = 127.0.0.1 #allow_notify = 127.0.0.1
#masters = 127.0.0.1:5354 #masters = 127.0.0.1:5354
#backend_driver = fake #backend_driver = fake
#transfer_source = None
#----------------------- #-----------------------
@ -269,3 +270,4 @@ debug = False
#rndc_config_file = /etc/rndc.conf #rndc_config_file = /etc/rndc.conf
#rndc_key_file = /etc/rndc.key #rndc_key_file = /etc/rndc.key
#zone_file_path = $state_path/zones #zone_file_path = $state_path/zones
#query_destination = 127.0.0.1