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'),
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')

View File

@ -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"))

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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