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:
parent
1924d8e7e9
commit
eefeb1414e
@ -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')
|
||||||
|
@ -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"))
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user