Merge "Remove deprecated designate-agent"
This commit is contained in:
commit
ac556c65b5
@ -1,110 +0,0 @@
|
||||
#! /bin/bash
|
||||
### BEGIN INIT INFO
|
||||
# Provides: tinydns
|
||||
# Required-Start: $local_fs $remote_fs $network
|
||||
# Required-Stop: $local_fs $remote_fs $network
|
||||
# Should-Start: $syslog
|
||||
# Should-Stop: $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: tinydns daemon processes
|
||||
# Description: Start the TinyDNS resolver
|
||||
### END INIT INFO
|
||||
|
||||
# Documentation
|
||||
# man tinydns
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
NAME=tinydns
|
||||
DAEMON=/usr/bin/$NAME
|
||||
DAEMON_USER=djbdns
|
||||
DESC="the tinydns daemon"
|
||||
ROOTDIR=/var/lib/djbdns
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin
|
||||
LAUNCHER=/usr/bin/envuidgid
|
||||
LAUNCHER_ARGS="$DAEMON_USER envdir ./env softlimit -d300000 $DAEMON"
|
||||
|
||||
PIDFILE=/run/$NAME.pid
|
||||
|
||||
# Exit if executable is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
set -x
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if [ ! -d "$ROOTDIR" ]; then
|
||||
log_action_msg "Not starting $DESC: $ROOTDIR is missing."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_action_begin_msg "Starting $DESC"
|
||||
|
||||
if start-stop-daemon --stop --signal 0 --quiet --pidfile $PIDFILE --exec $DAEMON; then
|
||||
log_action_end_msg 0 "already running"
|
||||
else
|
||||
if start-stop-daemon --start --verbose --make-pidfile --chdir $ROOTDIR --pidfile $PIDFILE --exec $LAUNCHER -- $LAUNCHER_ARGS
|
||||
then
|
||||
log_action_end_msg 0
|
||||
else
|
||||
log_action_end_msg 1
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
log_action_begin_msg "Stopping $DESC"
|
||||
pid=$(cat $PIDFILE 2>/dev/null) || true
|
||||
if test ! -f $PIDFILE -o -z "$pid"; then
|
||||
log_action_end_msg 0 "not running - there is no $PIDFILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if start-stop-daemon --stop --signal INT --quiet --pidfile $PIDFILE --exec $DAEMON; then
|
||||
rm -f $PIDFILE
|
||||
elif kill -0 $pid 2>/dev/null; then
|
||||
log_action_end_msg 1 "Is $pid not $NAME? Is $DAEMON a different binary now?"
|
||||
exit 1
|
||||
else
|
||||
log_action_end_msg 1 "$DAEMON died: process $pid not running; or permission denied"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
reload)
|
||||
echo "Not implemented, use restart"
|
||||
exit 1
|
||||
;;
|
||||
restart|force-reload)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if test ! -r $(dirname $PIDFILE); then
|
||||
log_failure_msg "cannot read PID file $PIDFILE"
|
||||
exit 4
|
||||
fi
|
||||
pid=$(cat $PIDFILE 2>/dev/null) || true
|
||||
if test ! -f $PIDFILE -o -z "$pid"; then
|
||||
log_failure_msg "$NAME is not running"
|
||||
exit 3
|
||||
fi
|
||||
if ps "$pid" >/dev/null 2>&1; then
|
||||
log_success_msg "$NAME is running"
|
||||
exit 0
|
||||
else
|
||||
log_failure_msg "$NAME is not running"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_action_msg "Usage: $0 {start|stop|restart|force-reload|status}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
@ -1,44 +0,0 @@
|
||||
#
|
||||
# Replace /var/lib/djbdns if needed
|
||||
#
|
||||
|
||||
[Unit]
|
||||
Description=tinydns DNS resolver
|
||||
Documentation=man:tinydns
|
||||
Documentation=https://cr.yp.to/djbdns.html
|
||||
After=network.target
|
||||
Requires=network.target
|
||||
Wants=network.target
|
||||
ConditionPathExists=/var/lib/djbdns
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile=/run/tinydns.pid
|
||||
Environment="ROOT=/var/lib/djbdns"
|
||||
ExecStart=/usr/bin/tinydns
|
||||
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry=TERM/5/KILL/5 --pidfile /run/tinydns.pid
|
||||
TimeoutStopSec=30
|
||||
KillMode=mixed
|
||||
|
||||
PermissionsStartOnly=true
|
||||
Restart=on-abnormal
|
||||
RestartSec=2s
|
||||
LimitNOFILE=65536
|
||||
|
||||
WorkingDirectory=/var/lib/djbdns
|
||||
User=$ug_name
|
||||
Group=$ug_name
|
||||
|
||||
# Hardening
|
||||
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN CAP_FOWNER
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
PrivateTmp=yes
|
||||
ProtectHome=yes
|
||||
ProtectSystem=full
|
||||
# TODO: restrict ReadOnlyDirectories
|
||||
ReadOnlyDirectories=/
|
||||
ReadWriteDirectories=-/var/lib/djbdns
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,258 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
agent.handler
|
||||
~~~~~~~~~~~~~
|
||||
Typically runs on the resolver hosts. Listen for incoming DNS requests
|
||||
on a port different than 53 and execute create_zone/delete_zone on the
|
||||
backend adaptor (e.g. Bind9)
|
||||
|
||||
Configured in [service:agent]
|
||||
"""
|
||||
|
||||
import dns
|
||||
import dns.flags
|
||||
import dns.message
|
||||
import dns.opcode
|
||||
import dns.rcode
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend import agent_backend
|
||||
import designate.backend.private_codes as pcodes
|
||||
from designate import dnsutils
|
||||
from designate import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class RequestHandler(object):
|
||||
def __init__(self):
|
||||
self.masters = []
|
||||
for server in CONF['service:agent'].masters:
|
||||
raw_server = utils.split_host_port(server)
|
||||
master = {'host': raw_server[0], 'port': int(raw_server[1])}
|
||||
self.masters.append(master)
|
||||
|
||||
LOG.info("Agent masters: %(masters)s", {'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)
|
||||
|
||||
# TODO(johnsom) Remove this after the agents framework is removed or
|
||||
# the protocol has been updated to not use an unassigned opcode(14).
|
||||
dns.opcode.Opcode = pcodes.OpcodeWith14
|
||||
|
||||
def __call__(self, request):
|
||||
"""
|
||||
:param request: DNS Request Message
|
||||
:return: DNS Response Message
|
||||
"""
|
||||
# TODO(Tim): Handle multiple questions
|
||||
rdtype = request.question[0].rdtype
|
||||
rdclass = request.question[0].rdclass
|
||||
opcode = request.opcode()
|
||||
if opcode == dns.opcode.NOTIFY:
|
||||
response = self._handle_notify(request)
|
||||
elif opcode == pcodes.CC:
|
||||
if rdclass == pcodes.CLASSCC:
|
||||
if rdtype == pcodes.CREATE:
|
||||
response = self._handle_create(request)
|
||||
elif rdtype == pcodes.DELETE:
|
||||
response = self._handle_delete(request)
|
||||
else:
|
||||
response = self._handle_query_error(request,
|
||||
dns.rcode.REFUSED)
|
||||
else:
|
||||
response = self._handle_query_error(request, dns.rcode.REFUSED)
|
||||
else:
|
||||
# Unhandled OpCodes include STATUS, QUERY, IQUERY, UPDATE
|
||||
response = self._handle_query_error(request, dns.rcode.REFUSED)
|
||||
|
||||
# TODO(Tim): Answer Type 65XXX queries
|
||||
yield response
|
||||
return
|
||||
|
||||
def _handle_query_error(self, request, rcode):
|
||||
"""
|
||||
Construct an error response with the rcode passed in.
|
||||
:param request: The decoded request from the wire.
|
||||
:param rcode: The response code to send back.
|
||||
:return: A dns response message with the response code set to rcode
|
||||
"""
|
||||
response = dns.message.make_response(request)
|
||||
response.set_rcode(rcode)
|
||||
|
||||
return response
|
||||
|
||||
def _handle_create(self, request):
|
||||
response = dns.message.make_response(request)
|
||||
|
||||
question = request.question[0]
|
||||
requester = request.environ['addr'][0]
|
||||
zone_name = question.name.to_text()
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
if not self._allowed(request, requester, "CREATE", zone_name):
|
||||
response.set_rcode(dns.rcode.from_text("REFUSED"))
|
||||
return response
|
||||
|
||||
serial = self.backend.find_zone_serial(zone_name)
|
||||
|
||||
if serial is not None:
|
||||
# Does this warrant a warning?
|
||||
# There is a race condition between checking if the zone exists
|
||||
# and creating it.
|
||||
LOG.warning("Not creating %(name)s, zone already exists",
|
||||
{'name': zone_name})
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
return response
|
||||
|
||||
LOG.debug("Received %(verb)s for %(name)s from %(host)s",
|
||||
{'verb': "CREATE", 'name': zone_name, 'host': requester})
|
||||
|
||||
try:
|
||||
# Receive an AXFR from MiniDNS to populate the zone
|
||||
zone = dnsutils.do_axfr(zone_name, self.masters,
|
||||
source=self.transfer_source)
|
||||
self.backend.create_zone(zone)
|
||||
except Exception as e:
|
||||
# TODO(Federico) unknown exceptions should be logged with a full
|
||||
# traceback. Same in the other methods.
|
||||
LOG.error("Exception while creating zone %r", e)
|
||||
response.set_rcode(dns.rcode.from_text("SERVFAIL"))
|
||||
return response
|
||||
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
|
||||
return response
|
||||
|
||||
def _handle_notify(self, request):
|
||||
"""
|
||||
Constructs the response to a NOTIFY and acts accordingly on it.
|
||||
|
||||
* Decodes the NOTIFY
|
||||
* Checks if the master sending the NOTIFY is allowed to notify
|
||||
* Does a serial check to see if further action needs to be taken
|
||||
* Kicks off an AXFR and returns a valid response
|
||||
"""
|
||||
response = dns.message.make_response(request)
|
||||
|
||||
question = request.question[0]
|
||||
requester = request.environ['addr'][0]
|
||||
zone_name = question.name.to_text()
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
if not self._allowed(request, requester, "NOTIFY", zone_name):
|
||||
response.set_rcode(dns.rcode.from_text("REFUSED"))
|
||||
return response
|
||||
|
||||
serial = self.backend.find_zone_serial(zone_name)
|
||||
|
||||
if serial is None:
|
||||
LOG.warning("Refusing NOTIFY for %(name)s, doesn't exist",
|
||||
{'name': zone_name})
|
||||
response.set_rcode(dns.rcode.from_text("REFUSED"))
|
||||
return response
|
||||
|
||||
LOG.debug("Received %(verb)s for %(name)s from %(host)s",
|
||||
{'verb': "NOTIFY", 'name': zone_name, 'host': requester})
|
||||
|
||||
# According to RFC we should query the server that sent the NOTIFY
|
||||
# TODO(Tim): Reenable this when it makes more sense
|
||||
# resolver = dns.resolver.Resolver()
|
||||
# resolver.nameservers = [requester]
|
||||
# This assumes that the Master is running on port 53
|
||||
# soa_answer = resolver.query(zone_name, 'SOA')
|
||||
# Check that the serial is < serial above
|
||||
|
||||
try:
|
||||
zone = dnsutils.do_axfr(zone_name, self.masters,
|
||||
source=self.transfer_source)
|
||||
self.backend.update_zone(zone)
|
||||
except Exception:
|
||||
response.set_rcode(dns.rcode.from_text("SERVFAIL"))
|
||||
return response
|
||||
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
|
||||
return response
|
||||
|
||||
def _handle_delete(self, request):
|
||||
"""
|
||||
Constructs the response to a DELETE and acts accordingly on it.
|
||||
|
||||
* Decodes the message for zone name
|
||||
* Checks if the master sending the DELETE is in the allowed notify list
|
||||
* Checks if the zone exists (maybe?)
|
||||
* Kicks a call to the backend to delete the zone in question
|
||||
"""
|
||||
response = dns.message.make_response(request)
|
||||
|
||||
question = request.question[0]
|
||||
requester = request.environ['addr'][0]
|
||||
zone_name = question.name.to_text()
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
if not self._allowed(request, requester, "DELETE", zone_name):
|
||||
response.set_rcode(dns.rcode.from_text("REFUSED"))
|
||||
return response
|
||||
|
||||
serial = self.backend.find_zone_serial(zone_name)
|
||||
|
||||
if serial is None:
|
||||
LOG.warning("Not deleting %(name)s, zone doesn't exist",
|
||||
{'name': zone_name})
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
return response
|
||||
|
||||
LOG.debug("Received DELETE for %(name)s from %(host)s",
|
||||
{'name': zone_name, 'host': requester})
|
||||
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
|
||||
# Call into the backend to Delete
|
||||
try:
|
||||
self.backend.delete_zone(zone_name)
|
||||
except Exception:
|
||||
response.set_rcode(dns.rcode.from_text("SERVFAIL"))
|
||||
return response
|
||||
|
||||
return response
|
||||
|
||||
def _allowed(self, request, requester, op, zone_name):
|
||||
# If there are no explict notifiers specified, allow all
|
||||
if not self.allow_notify:
|
||||
return True
|
||||
|
||||
if requester not in self.allow_notify:
|
||||
LOG.warning("%(verb)s for %(name)s from %(server)s refused",
|
||||
{'verb': op, 'name': zone_name, 'server': requester})
|
||||
return False
|
||||
|
||||
return True
|
@ -1,86 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
agent.service
|
||||
~~~~~~~~~~~~~
|
||||
Typically runs on the resolver hosts. Listen for incoming DNS requests
|
||||
on a port different than 53 and execute create_zone/delete_zone on the
|
||||
backend adaptor (e.g. Bind9)
|
||||
|
||||
Configured in [service:agent]
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from designate.agent import handler
|
||||
from designate.backend import agent_backend
|
||||
from designate.conf.agent import DEFAULT_AGENT_PORT
|
||||
from designate import dnsmiddleware
|
||||
from designate import service
|
||||
from designate import utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
_dns_default_port = DEFAULT_AGENT_PORT
|
||||
|
||||
def __init__(self):
|
||||
super(Service, self).__init__(
|
||||
self.service_name, threads=cfg.CONF['service:agent'].threads
|
||||
)
|
||||
|
||||
warnings.warn('The designate agent service is deprecated as of the '
|
||||
'Antelope (2023.1) release and will be removed in the '
|
||||
'"C" release.', DeprecationWarning)
|
||||
|
||||
self.dns_service = service.DNSService(
|
||||
self.dns_application, self.tg,
|
||||
cfg.CONF['service:agent'].listen,
|
||||
cfg.CONF['service:agent'].tcp_backlog,
|
||||
cfg.CONF['service:agent'].tcp_recv_timeout,
|
||||
)
|
||||
|
||||
backend_driver = cfg.CONF['service:agent'].backend_driver
|
||||
self.backend = agent_backend.get_backend(backend_driver, self)
|
||||
|
||||
def start(self):
|
||||
super(Service, self).start()
|
||||
self.dns_service.start()
|
||||
self.backend.start()
|
||||
|
||||
def stop(self, graceful=True):
|
||||
self.dns_service.stop()
|
||||
self.backend.stop()
|
||||
super(Service, self).stop(graceful)
|
||||
|
||||
@property
|
||||
def service_name(self):
|
||||
return 'agent'
|
||||
|
||||
@property
|
||||
@utils.cache_result
|
||||
def dns_application(self):
|
||||
# Create an instance of the RequestHandler class
|
||||
application = handler.RequestHandler()
|
||||
if cfg.CONF['service:agent'].notify_delay > 0.0:
|
||||
application = dnsmiddleware.LimitNotifyMiddleware(application)
|
||||
application = dnsmiddleware.SerializationMiddleware(application)
|
||||
|
||||
return application
|
@ -1,173 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
backend.agent
|
||||
~~~~~~~~~~~~~
|
||||
Agent backend for Pool Manager.
|
||||
Sends DNS requests to a remote agent using private OPCODEs to trigger
|
||||
creation / deletion / update of zones.
|
||||
|
||||
Configured in the [service:pool_manager] section
|
||||
"""
|
||||
|
||||
import dns
|
||||
import dns.exception
|
||||
import dns.flags
|
||||
import dns.message
|
||||
import dns.opcode
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend import base
|
||||
from designate.backend import private_codes
|
||||
from designate.conf.agent import DEFAULT_AGENT_PORT
|
||||
from designate import dnsutils
|
||||
from designate import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class AgentPoolBackend(base.Backend):
|
||||
__plugin_name__ = 'agent'
|
||||
__backend_status__ = 'untested'
|
||||
|
||||
def __init__(self, target):
|
||||
super(AgentPoolBackend, self).__init__(target)
|
||||
self.host = self.options.get('host', '127.0.0.1')
|
||||
self.port = int(self.options.get('port', DEFAULT_AGENT_PORT))
|
||||
self.timeout = CONF['service:worker'].poll_timeout
|
||||
self.retry_interval = CONF['service:worker'].poll_retry_interval
|
||||
self.max_retries = CONF['service:worker'].poll_max_retries
|
||||
# FIXME: the agent retries creating zones without any interval
|
||||
|
||||
# TODO(johnsom) Remove this after the agents framework is removed or
|
||||
# the protocol has been updated to not use an unassigned opcode(14).
|
||||
dns.opcode.Opcode = private_codes.OpcodeWith14
|
||||
|
||||
def create_zone(self, context, zone):
|
||||
LOG.debug('Create Zone')
|
||||
response = self._make_and_send_dns_message(
|
||||
zone.name,
|
||||
self.timeout,
|
||||
private_codes.CC,
|
||||
private_codes.CREATE,
|
||||
private_codes.CLASSCC,
|
||||
self.host,
|
||||
self.port
|
||||
)
|
||||
if response is None:
|
||||
raise exceptions.Backend('Failed create_zone()')
|
||||
|
||||
def update_zone(self, context, zone):
|
||||
LOG.debug('Update Zone')
|
||||
|
||||
def delete_zone(self, context, zone):
|
||||
LOG.debug('Delete Zone')
|
||||
response = self._make_and_send_dns_message(
|
||||
zone.name,
|
||||
self.timeout,
|
||||
private_codes.CC,
|
||||
private_codes.DELETE,
|
||||
private_codes.CLASSCC,
|
||||
self.host,
|
||||
self.port
|
||||
)
|
||||
if response is None:
|
||||
raise exceptions.Backend('Failed delete_zone()')
|
||||
|
||||
def _make_and_send_dns_message(self, zone_name, timeout, opcode,
|
||||
rdatatype, rdclass, dest_ip,
|
||||
dest_port):
|
||||
dns_message = self._make_dns_message(
|
||||
zone_name, opcode, rdatatype, rdclass
|
||||
)
|
||||
LOG.info(
|
||||
"Sending '%(msg)s' for '%(zone)s' to '%(server)s:%(port)d'.",
|
||||
{
|
||||
'msg': str(opcode),
|
||||
'zone': zone_name,
|
||||
'server': dest_ip,
|
||||
'port': dest_port
|
||||
}
|
||||
)
|
||||
try:
|
||||
response = dnsutils.send_dns_message(
|
||||
dns_message, dest_ip, port=dest_port, timeout=timeout
|
||||
)
|
||||
# Check that we actually got a NOERROR in the rcode and and an
|
||||
# authoritative answer
|
||||
if not (response.flags & dns.flags.AA) or dns.rcode.from_flags(
|
||||
response.flags, response.ednsflags) != dns.rcode.NOERROR:
|
||||
LOG.warning(
|
||||
"Failed to get expected response while trying to "
|
||||
"send '%(msg)s' for '%(zone)s' to "
|
||||
"'%(server)s:%(port)d'. Response message: %(resp)s",
|
||||
{
|
||||
'msg': str(opcode),
|
||||
'zone': zone_name,
|
||||
'server': dest_ip,
|
||||
'port': dest_port,
|
||||
'resp': str(response)
|
||||
}
|
||||
)
|
||||
response = None
|
||||
except dns.exception.Timeout:
|
||||
LOG.warning(
|
||||
"Got Timeout while trying to send '%(msg)s' for "
|
||||
"'%(zone)s' to '%(server)s:%(port)d'. "
|
||||
"Timeout='%(timeout)d' seconds.",
|
||||
{
|
||||
'msg': str(opcode),
|
||||
'zone': zone_name,
|
||||
'server': dest_ip,
|
||||
'port': dest_port,
|
||||
'timeout': timeout,
|
||||
}
|
||||
)
|
||||
response = None
|
||||
except dns.query.BadResponse:
|
||||
LOG.warning(
|
||||
"Got BadResponse while trying to send '%(msg)s' for "
|
||||
"'%(zone)s' to '%(server)s:%(port)d'. "
|
||||
"Timeout='%(timeout)d' seconds.",
|
||||
{
|
||||
'msg': str(opcode),
|
||||
'zone': zone_name,
|
||||
'server': dest_ip,
|
||||
'port': dest_port,
|
||||
'timeout': timeout,
|
||||
}
|
||||
)
|
||||
response = None
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _make_dns_message(zone_name, opcode, rdatatype, rdclass):
|
||||
dns_message = dns.message.make_query(zone_name, rdatatype,
|
||||
rdclass=rdclass)
|
||||
dns_message.flags = 0
|
||||
|
||||
dns_message.set_opcode(opcode)
|
||||
dns_message.flags |= dns.flags.AA
|
||||
|
||||
return dns_message
|
@ -1,28 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_backend(backend_driver, agent_service):
|
||||
LOG.debug("Loading backend driver: %s", backend_driver)
|
||||
|
||||
cls = base.AgentBackend.get_driver(backend_driver)
|
||||
|
||||
return cls(agent_service)
|
@ -1,55 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 abc
|
||||
|
||||
from debtcollector import removals
|
||||
|
||||
from designate.plugin import DriverPlugin
|
||||
|
||||
|
||||
@removals.removed_class('AgentBackend')
|
||||
class AgentBackend(DriverPlugin):
|
||||
"""Base class for backend implementations"""
|
||||
__plugin_type__ = 'backend'
|
||||
__plugin_ns__ = 'designate.backend.agent_backend'
|
||||
|
||||
def __init__(self, agent_service):
|
||||
super(AgentBackend, self).__init__()
|
||||
self.agent_service = agent_service
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_zone_serial(self, zone_name):
|
||||
"""Find a DNS Zone"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_zone(self, zone):
|
||||
"""Create a DNS zone"""
|
||||
"""Zone is a DNSPython Zone object"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_zone(self, zone):
|
||||
"""Update a DNS zone"""
|
||||
"""Zone is a DNSPython Zone object"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_zone(self, zone_name, zone_params):
|
||||
"""Delete a DNS zone"""
|
@ -1,137 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 os
|
||||
import warnings
|
||||
|
||||
import dns
|
||||
import dns.resolver
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
|
||||
CFG_GROUP_NAME = 'backend:agent:bind9'
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Bind9Backend(base.AgentBackend):
|
||||
__plugin_name__ = 'bind9'
|
||||
__backend_status__ = 'untested'
|
||||
|
||||
def __init__(self, agent_service):
|
||||
super(Bind9Backend, self).__init__(agent_service)
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
def start(self):
|
||||
LOG.info("Started bind9 backend")
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
LOG.debug("Finding %s", zone_name)
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.nameservers = [cfg.CONF[CFG_GROUP_NAME].query_destination]
|
||||
try:
|
||||
rdata = resolver.query(zone_name, 'SOA')[0]
|
||||
except Exception:
|
||||
return None
|
||||
return rdata.serial
|
||||
|
||||
def create_zone(self, zone):
|
||||
LOG.debug("Creating %s", zone.origin.to_text())
|
||||
self._sync_zone(zone, new_zone_flag=True)
|
||||
|
||||
def update_zone(self, zone):
|
||||
LOG.debug("Updating %s", zone.origin.to_text())
|
||||
self._sync_zone(zone)
|
||||
|
||||
def delete_zone(self, zone_name):
|
||||
LOG.debug('Delete Zone: %s' % zone_name)
|
||||
|
||||
rndc_op = 'delzone'
|
||||
# RNDC doesn't like the trailing dot on the zone name
|
||||
rndc_call = self._rndc_base() + [rndc_op, zone_name.rstrip('.')]
|
||||
|
||||
utils.execute(*rndc_call)
|
||||
|
||||
def _rndc_base(self):
|
||||
rndc_call = [
|
||||
'rndc',
|
||||
'-s', cfg.CONF[CFG_GROUP_NAME].rndc_host,
|
||||
'-p', str(cfg.CONF[CFG_GROUP_NAME].rndc_port),
|
||||
]
|
||||
|
||||
if cfg.CONF[CFG_GROUP_NAME].rndc_config_file:
|
||||
rndc_call.extend(['-c',
|
||||
cfg.CONF[CFG_GROUP_NAME].rndc_config_file])
|
||||
|
||||
if cfg.CONF[CFG_GROUP_NAME].rndc_key_file:
|
||||
rndc_call.extend(['-k',
|
||||
cfg.CONF[CFG_GROUP_NAME].rndc_key_file])
|
||||
|
||||
return rndc_call
|
||||
|
||||
def _sync_zone(self, zone, new_zone_flag=False):
|
||||
"""Sync a single zone's zone file and reload bind config"""
|
||||
|
||||
# NOTE: Different versions of BIND9 behave differently with a trailing
|
||||
# dot, so we're just going to take it off.
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
# NOTE: Only one thread should be working with the Zonefile at a given
|
||||
# time. The sleep(1) below introduces a not insignificant risk
|
||||
# of more than 1 thread working with a zonefile at a given time.
|
||||
with lockutils.lock('bind9-%s' % zone_name):
|
||||
LOG.debug('Synchronising Zone: %s' % zone_name)
|
||||
|
||||
zone_path = cfg.CONF[CFG_GROUP_NAME].zone_file_path
|
||||
|
||||
output_path = os.path.join(zone_path,
|
||||
'%s.zone' % zone_name)
|
||||
|
||||
zone.to_file(output_path, relativize=False)
|
||||
|
||||
rndc_call = self._rndc_base()
|
||||
|
||||
if new_zone_flag:
|
||||
rndc_op = [
|
||||
'addzone',
|
||||
'%s { type master; file "%s"; };' % (zone_name,
|
||||
output_path),
|
||||
]
|
||||
rndc_call.extend(rndc_op)
|
||||
else:
|
||||
rndc_op = 'reload'
|
||||
rndc_call.extend([rndc_op])
|
||||
rndc_call.extend([zone_name])
|
||||
|
||||
LOG.debug('Calling RNDC with: %s' % " ".join(rndc_call))
|
||||
self._execute_rndc(rndc_call)
|
||||
|
||||
def _execute_rndc(self, rndc_call):
|
||||
try:
|
||||
LOG.debug('Executing RNDC call: %s' % " ".join(rndc_call))
|
||||
utils.execute(*rndc_call)
|
||||
except utils.processutils.ProcessExecutionError as e:
|
||||
LOG.debug('RNDC call failure: %s' % e)
|
||||
raise exceptions.Backend(e)
|
@ -1,242 +0,0 @@
|
||||
# Copyright 2015 Dyn Inc.
|
||||
#
|
||||
# Author: Yasha Bubnov <ybubnov@dyn.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 itertools
|
||||
import warnings
|
||||
|
||||
import dns.rdata
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
|
||||
CFG_GROUP_NAME = 'backend:agent:denominator'
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Denominator(object):
|
||||
|
||||
def __init__(self, config):
|
||||
super(Denominator, self).__init__()
|
||||
self.config = config
|
||||
|
||||
def update_record(self, zone, **kwargs):
|
||||
return self._execute(['record', '-z', zone, 'replace'], kwargs)
|
||||
|
||||
def create_record(self, zone, **kwargs):
|
||||
return self._execute(['record', '-z', zone, 'add'], kwargs)
|
||||
|
||||
def delete_record(self, zone, **kwargs):
|
||||
return self._execute(['record', '-z', zone, 'delete'], kwargs)
|
||||
|
||||
def get_record(self, zone, **kwargs):
|
||||
return self._execute(['record', '-z', zone, 'get'], kwargs)
|
||||
|
||||
def get_records(self, zone, **kwargs):
|
||||
return self._execute(['record', '-z', zone, 'list'], kwargs)
|
||||
|
||||
def create_zone(self, **kwargs):
|
||||
return self._execute(['zone', 'add'], kwargs)
|
||||
|
||||
def update_zone(self, **kwargs):
|
||||
return self._execute(['zone', 'update'], kwargs)
|
||||
|
||||
def delete_zone(self, **kwargs):
|
||||
return self._execute(['zone', 'delete'], kwargs)
|
||||
|
||||
def _params(self, **kwargs):
|
||||
params = [('--%s' % k, str(v)) for k, v in kwargs.items()]
|
||||
return list(itertools.chain(*params))
|
||||
|
||||
def _base(self):
|
||||
call = ['denominator', '-q', '-n', self.config.name]
|
||||
|
||||
# NOTE: When path to denominator configuration file is omitted,
|
||||
# ~/.denominatorconfig file will be used by default.
|
||||
if self.config.config_file:
|
||||
call.extend(['-C', self.config.config_file])
|
||||
return call
|
||||
|
||||
def _execute(self, op, kwargs):
|
||||
try:
|
||||
call = self._base() + op + self._params(**kwargs)
|
||||
LOG.debug(('Executing Denominator call: %s' % ' '.join(call)))
|
||||
|
||||
stdout, _ = utils.execute(*call)
|
||||
return stdout
|
||||
except utils.processutils.ProcessExecutionError as e:
|
||||
LOG.debug('Denominator call failure: %s' % e)
|
||||
raise exceptions.DesignateException(e)
|
||||
|
||||
|
||||
class DenominatorBackend(base.AgentBackend):
|
||||
__plugin_name__ = 'denominator'
|
||||
|
||||
__backend_status__ = 'untested'
|
||||
|
||||
def __init__(self, agent_service):
|
||||
super(DenominatorBackend, self).__init__(agent_service)
|
||||
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
self.denominator = Denominator(
|
||||
cfg.CONF[CFG_GROUP_NAME])
|
||||
|
||||
def start(self):
|
||||
LOG.info("Started Denominator backend")
|
||||
|
||||
def stop(self):
|
||||
LOG.info("Stopped Denominator backend")
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
LOG.debug("Finding %s", zone_name)
|
||||
|
||||
zone_name = zone_name.rstrip('.')
|
||||
output = self.denominator.get_record(
|
||||
zone=zone_name,
|
||||
type='SOA',
|
||||
name=zone_name)
|
||||
try:
|
||||
text = ' '.join(output.split()[3:])
|
||||
rdata = dns.rdata.from_text(dns.rdataclass.IN,
|
||||
dns.rdatatype.SOA,
|
||||
text)
|
||||
except Exception:
|
||||
return None
|
||||
return rdata.serial
|
||||
|
||||
def create_zone(self, zone):
|
||||
LOG.debug("Creating %s", zone.origin.to_text())
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
# Use SOA TTL as zone default TTL
|
||||
soa_record = zone.find_rrset(zone.origin, dns.rdatatype.SOA)
|
||||
rname = list(soa_record.items)[0].rname.derelativize(
|
||||
origin=zone.origin)
|
||||
|
||||
# Lock zone to prevent concurrent changes.
|
||||
with self._sync_zone(zone.origin):
|
||||
# NOTE: If zone already exists, denominator will update it with
|
||||
# new values, in other a duplicate zone will be created if
|
||||
# provider supports such functionality.
|
||||
self.denominator.create_zone(
|
||||
name=zone_name,
|
||||
ttl=soa_record.ttl,
|
||||
email=rname)
|
||||
|
||||
# Add records one by one.
|
||||
for name, ttl, rtype, data in self._iterate_records(zone):
|
||||
# Some providers do not support creation of SOA record.
|
||||
rdatatype = dns.rdatatype.from_text(rtype)
|
||||
if rdatatype == dns.rdatatype.SOA:
|
||||
continue
|
||||
|
||||
self.denominator.create_record(
|
||||
zone=zone_name,
|
||||
name=name,
|
||||
type=rtype,
|
||||
ttl=ttl,
|
||||
data=data)
|
||||
|
||||
def update_zone(self, zone):
|
||||
LOG.debug("Updating %s", zone.origin)
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
soa_record = zone.find_rrset(zone.origin, dns.rdatatype.SOA)
|
||||
rname = list(soa_record.items)[0].rname.derelativize(
|
||||
origin=zone.origin)
|
||||
|
||||
with self._sync_zone(zone.origin):
|
||||
# Update zone with a new parameters
|
||||
self.denominator.update_zone(
|
||||
id=zone_name,
|
||||
ttl=soa_record.ttl,
|
||||
email=rname)
|
||||
|
||||
# Fetch records to create a differential update of a zone.
|
||||
output = self.denominator.get_records(zone_name)
|
||||
subzones = dict()
|
||||
|
||||
# subzones dict will contain names of subzones without
|
||||
# trailing dot.
|
||||
for raw in output.splitlines():
|
||||
data = raw.split()
|
||||
name, rtype = data[0], data[1]
|
||||
|
||||
rtypes = subzones.get(name, set())
|
||||
rtypes.add(rtype)
|
||||
subzones[name] = rtypes
|
||||
|
||||
for name, ttl, rtype, data in self._iterate_records(zone):
|
||||
record_action = self.denominator.create_record
|
||||
|
||||
if name in subzones and rtype in subzones[name]:
|
||||
# When RR set already exists, replace it with a new one.
|
||||
rdatatype = dns.rdatatype.from_text(rtype)
|
||||
record_action = self.denominator.update_record
|
||||
|
||||
# So next call will ADD a new record to record set
|
||||
# instead of replacing of the existing one.
|
||||
subzones[name].remove(rtype)
|
||||
|
||||
# NOTE: DynECT does not support deleting of the SOA
|
||||
# record. Skip updating of the SOA record.
|
||||
if rdatatype == dns.rdatatype.SOA:
|
||||
continue
|
||||
|
||||
record_action(zone=zone_name,
|
||||
name=name,
|
||||
type=rtype,
|
||||
ttl=ttl,
|
||||
data=data)
|
||||
|
||||
# Remaining records should be deleted
|
||||
for name, types in subzones.items():
|
||||
for rtype in types:
|
||||
self.denominator.delete_record(
|
||||
zone=zone_name, id=name, type=rtype)
|
||||
|
||||
def delete_zone(self, zone_name):
|
||||
LOG.debug('Delete Zone: %s' % zone_name)
|
||||
|
||||
with self._sync_zone(zone_name):
|
||||
self.denominator.delete_zone(id=zone_name)
|
||||
|
||||
def _sync_zone(self, zone_name):
|
||||
LOG.debug('Synchronising zone: %s' % zone_name)
|
||||
return lockutils.lock('denominator-%s' % zone_name)
|
||||
|
||||
def _iterate_records(self, zone):
|
||||
for rname, ttl, rdata in zone.iterate_rdatas():
|
||||
name = rname.derelativize(origin=zone.origin)
|
||||
name = name.to_text(omit_final_dot=True)
|
||||
if isinstance(name, bytes):
|
||||
name = name.decode('utf-8')
|
||||
|
||||
data = rdata.to_text(origin=zone.origin, relativize=False)
|
||||
yield name, ttl, dns.rdatatype.to_text(rdata.rdtype), data
|
@ -1,333 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
backend.agent_backend.impl_djbdns
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Djbdns DNS agent backend
|
||||
|
||||
Create, update, delete zones locally on a Djbdns DNS resolver using the
|
||||
axfr-get utility.
|
||||
|
||||
`Djbdns User documentation <../../admin/backends/djbdns_agent.html>`_
|
||||
|
||||
.. WARNING::
|
||||
|
||||
Untested, do not use in production.
|
||||
|
||||
|
||||
Configured in [service:agent:djbdns]
|
||||
|
||||
Requires rootwrap (or equivalent sudo privileges) to execute:
|
||||
- tcpclient
|
||||
- axfr-get
|
||||
- tinydns-data
|
||||
|
||||
"""
|
||||
|
||||
import errno
|
||||
import glob
|
||||
import os
|
||||
import random
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
import dns
|
||||
import dns.resolver
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_concurrency.processutils import ProcessExecutionError
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
from designate.utils import execute
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CFG_GROUP_NAME = 'backend:agent:djbdns'
|
||||
# rootwrap requires a command name instead of full path
|
||||
TCPCLIENT_DEFAULT_PATH = 'tcpclient'
|
||||
AXFR_GET_DEFAULT_PATH = 'axfr-get'
|
||||
TINYDNS_DATA_DEFAULT_PATH = 'tinydns-data'
|
||||
|
||||
TINYDNS_DATADIR_DEFAULT_PATH = '/var/lib/djbdns'
|
||||
SOA_QUERY_TIMEOUT = 1
|
||||
|
||||
|
||||
# TODO(Federico) on zone creation and update, agent.handler unnecessarily
|
||||
# perfors AXFR from MiniDNS to the Agent to populate the `zone` argument
|
||||
# (needed by the Bind backend)
|
||||
|
||||
|
||||
def filter_exceptions(fn):
|
||||
# Let Backend() exceptions pass through, log out every other exception
|
||||
# and re-raise it as Backend()
|
||||
def wrapper(*a, **kw):
|
||||
try:
|
||||
return fn(*a, **kw)
|
||||
except exceptions.Backend:
|
||||
raise
|
||||
except Exception as e:
|
||||
LOG.error("Unhandled exception %s", e, exc_info=True)
|
||||
raise exceptions.Backend(str(e))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class DjbdnsBackend(base.AgentBackend):
|
||||
__plugin_name__ = 'djbdns'
|
||||
__backend_status__ = 'experimental'
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
"""Configure the backend"""
|
||||
super(DjbdnsBackend, self).__init__(*a, **kw)
|
||||
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
conf = cfg.CONF[CFG_GROUP_NAME]
|
||||
|
||||
self._resolver = dns.resolver.Resolver(configure=False)
|
||||
self._resolver.timeout = SOA_QUERY_TIMEOUT
|
||||
self._resolver.lifetime = SOA_QUERY_TIMEOUT
|
||||
self._resolver.nameservers = [conf.query_destination]
|
||||
self._masters = [utils.split_host_port(ns)
|
||||
for ns in cfg.CONF['service:agent'].masters]
|
||||
LOG.info("Resolvers: %r", self._resolver.nameservers)
|
||||
LOG.info("AXFR masters: %r", self._masters)
|
||||
if not self._masters:
|
||||
raise exceptions.Backend("Missing agent AXFR masters")
|
||||
|
||||
self._tcpclient_cmd_name = conf.tcpclient_cmd_name
|
||||
self._axfr_get_cmd_name = conf.axfr_get_cmd_name
|
||||
|
||||
# Directory where data.cdb lives, usually /var/lib/djbdns/root
|
||||
tinydns_root_dir = os.path.join(conf.tinydns_datadir, 'root')
|
||||
|
||||
# Usually /var/lib/djbdns/root/data.cdb
|
||||
self._tinydns_cdb_filename = os.path.join(tinydns_root_dir, 'data.cdb')
|
||||
LOG.info("data.cdb path: %r", self._tinydns_cdb_filename)
|
||||
|
||||
# Where the agent puts the zone datafiles,
|
||||
# usually /var/lib/djbdns/datafiles
|
||||
self._datafiles_dir = datafiles_dir = os.path.join(
|
||||
conf.tinydns_datadir,
|
||||
'datafiles')
|
||||
self._datafiles_tmp_path_tpl = os.path.join(datafiles_dir, "%s.ztmp")
|
||||
self._datafiles_path_tpl = os.path.join(datafiles_dir, "%s.zonedata")
|
||||
self._datafiles_path_glob = self._datafiles_path_tpl % '*'
|
||||
|
||||
self._check_dirs(tinydns_root_dir, datafiles_dir)
|
||||
|
||||
@staticmethod
|
||||
def _check_dirs(*dirnames):
|
||||
"""Check if directories are writable
|
||||
"""
|
||||
for dn in dirnames:
|
||||
if not os.path.isdir(dn):
|
||||
raise exceptions.Backend("Missing directory %s" % dn)
|
||||
if not os.access(dn, os.W_OK):
|
||||
raise exceptions.Backend("Directory not writable: %s" % dn)
|
||||
|
||||
def start(self):
|
||||
"""Start the backend"""
|
||||
LOG.info("Started djbdns backend")
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
"""Query the local resolver for a zone
|
||||
Times out after SOA_QUERY_TIMEOUT
|
||||
"""
|
||||
LOG.debug("Finding %s", zone_name)
|
||||
try:
|
||||
rdata = self._resolver.query(
|
||||
zone_name, rdtype=dns.rdatatype.SOA)[0]
|
||||
return rdata.serial
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _concatenate_zone_datafiles(data_fn, path_glob):
|
||||
"""Concatenate all zone datafiles into 'data'
|
||||
"""
|
||||
with open(data_fn, 'w') as data_f:
|
||||
zone_cnt = 0
|
||||
for zone_fn in glob.glob(path_glob):
|
||||
zone_cnt += 1
|
||||
with open(zone_fn) as zf:
|
||||
data_f.write(zf.read())
|
||||
|
||||
LOG.info("Loaded %d zone datafiles.", zone_cnt)
|
||||
|
||||
def _rebuild_data_cdb(self):
|
||||
"""Rebuild data.cdb file from zone datafiles
|
||||
Requires global lock
|
||||
|
||||
On zone creation, axfr-get creates datafiles atomically by doing
|
||||
rename. On zone deletion, os.remove deletes the file atomically
|
||||
Globbing and reading the datafiles can be done without locking on
|
||||
them.
|
||||
The data and data.cdb files are written into a unique temp directory
|
||||
"""
|
||||
|
||||
tmpdir = tempfile.mkdtemp(dir=self._datafiles_dir)
|
||||
data_fn = os.path.join(tmpdir, 'data')
|
||||
tmp_cdb_fn = os.path.join(tmpdir, 'data.cdb')
|
||||
|
||||
try:
|
||||
self._concatenate_zone_datafiles(data_fn,
|
||||
self._datafiles_path_glob)
|
||||
# Generate the data.cdb file
|
||||
LOG.info("Updating data.cdb")
|
||||
LOG.debug("Convert %s to %s", data_fn, tmp_cdb_fn)
|
||||
try:
|
||||
out, err = execute(
|
||||
cfg.CONF[CFG_GROUP_NAME].tinydns_data_cmd_name,
|
||||
cwd=tmpdir
|
||||
)
|
||||
except ProcessExecutionError as e:
|
||||
LOG.error("Failed to generate data.cdb")
|
||||
LOG.error("Command output: %(out)r Stderr: %(err)r",
|
||||
{
|
||||
'out': e.stdout,
|
||||
'err': e.stderr
|
||||
})
|
||||
raise exceptions.Backend("Failed to generate data.cdb")
|
||||
|
||||
LOG.debug("Move %s to %s", tmp_cdb_fn, self._tinydns_cdb_filename)
|
||||
try:
|
||||
os.rename(tmp_cdb_fn, self._tinydns_cdb_filename)
|
||||
except OSError:
|
||||
os.remove(tmp_cdb_fn)
|
||||
LOG.error("Unable to move data.cdb to %s",
|
||||
self._tinydns_cdb_filename)
|
||||
raise exceptions.Backend("Unable to move data.cdb")
|
||||
|
||||
finally:
|
||||
try:
|
||||
os.remove(data_fn)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.removedirs(tmpdir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _perform_axfr_from_minidns(self, zone_name):
|
||||
"""Instruct axfr-get to request an AXFR from MiniDNS.
|
||||
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
zone_fn = self._datafiles_path_tpl % zone_name
|
||||
zone_tmp_fn = self._datafiles_tmp_path_tpl % zone_name
|
||||
|
||||
# Perform AXFR, create or update a zone datafile
|
||||
# No need to lock globally here.
|
||||
# Axfr-get creates the datafile atomically by doing rename
|
||||
mdns_hostname, mdns_port = random.choice(self._masters)
|
||||
with lockutils.lock("%s.lock" % zone_name):
|
||||
LOG.debug("writing to %s", zone_fn)
|
||||
cmd = (
|
||||
self._tcpclient_cmd_name,
|
||||
mdns_hostname,
|
||||
"%d" % mdns_port,
|
||||
self._axfr_get_cmd_name,
|
||||
zone_name,
|
||||
zone_fn,
|
||||
zone_tmp_fn
|
||||
)
|
||||
|
||||
LOG.debug("Executing AXFR as %r", ' '.join(cmd))
|
||||
try:
|
||||
out, err = execute(*cmd)
|
||||
except ProcessExecutionError as e:
|
||||
LOG.error("Error executing AXFR as %r", ' '.join(cmd))
|
||||
LOG.error("Command output: %(out)r Stderr: %(err)r",
|
||||
{
|
||||
'out': e.stdout,
|
||||
'err': e.stderr
|
||||
})
|
||||
raise exceptions.Backend(str(e))
|
||||
|
||||
finally:
|
||||
try:
|
||||
os.remove(zone_tmp_fn)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@filter_exceptions
|
||||
def create_zone(self, zone):
|
||||
"""Create a new Zone
|
||||
Do not raise exceptions if the zone already exists.
|
||||
|
||||
:param zone: zone to be created
|
||||
:type zone: raw pythondns Zone
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
LOG.debug("Creating %s", zone_name)
|
||||
# The zone might be already in place due to a race condition between
|
||||
# checking if the zone is there and creating it across different
|
||||
# greenlets
|
||||
|
||||
LOG.debug("Triggering initial AXFR from MiniDNS to Djbdns for %s",
|
||||
zone_name)
|
||||
self._perform_axfr_from_minidns(zone_name)
|
||||
self._rebuild_data_cdb()
|
||||
|
||||
@filter_exceptions
|
||||
def update_zone(self, zone):
|
||||
"""Instruct Djbdns DNS to perform AXFR from MiniDNS
|
||||
|
||||
:param zone: zone to be created
|
||||
:type zone: raw pythondns Zone
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
LOG.debug("Triggering AXFR from MiniDNS to Djbdns for %s", zone_name)
|
||||
self._perform_axfr_from_minidns(zone_name)
|
||||
self._rebuild_data_cdb()
|
||||
|
||||
@filter_exceptions
|
||||
def delete_zone(self, zone_name):
|
||||
"""Delete a new Zone
|
||||
Do not raise exceptions if the zone does not exist.
|
||||
|
||||
:param zone_name: zone name
|
||||
:type zone_name: str
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
zone_name = zone_name.rstrip('.')
|
||||
LOG.debug('Deleting Zone: %s', zone_name)
|
||||
zone_fn = self._datafiles_path_tpl % zone_name
|
||||
try:
|
||||
os.remove(zone_fn)
|
||||
LOG.debug('Deleted Zone: %s', zone_name)
|
||||
except OSError as e:
|
||||
if errno.ENOENT == e.errno:
|
||||
LOG.info("Zone datafile %s was already deleted", zone_fn)
|
||||
return
|
||||
|
||||
raise
|
||||
|
||||
self._rebuild_data_cdb()
|
@ -1,53 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 warnings
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FakeBackend(base.AgentBackend):
|
||||
__plugin_name__ = 'fake'
|
||||
|
||||
def __init__(self, agent_service):
|
||||
super(FakeBackend, self).__init__(agent_service)
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
def start(self):
|
||||
LOG.info("Started fake backend, Pool Manager will not work!")
|
||||
|
||||
def stop(self):
|
||||
LOG.info("Stopped fake backend")
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
LOG.debug("Finding %s", zone_name)
|
||||
return 0
|
||||
|
||||
def create_zone(self, zone):
|
||||
LOG.debug("Creating %s", zone.origin.to_text())
|
||||
|
||||
def update_zone(self, zone):
|
||||
LOG.debug("Updating %s", zone.origin.to_text())
|
||||
|
||||
def delete_zone(self, zone_name):
|
||||
LOG.debug('Delete Zone: %s', zone_name)
|
@ -1,240 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
backend.agent_backend.impl_gdnsd
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
gdnsd agent backend
|
||||
|
||||
Create, update, delete zones locally on a gdnsd resolver using the
|
||||
gdnsd utility.
|
||||
|
||||
Supported Knot versions: >= 2.1, < 3
|
||||
|
||||
`User documentation <../../admin/backends/gdnsd_agent.html>`_
|
||||
|
||||
.. WARNING::
|
||||
|
||||
Untested, do not use in production.
|
||||
|
||||
.. NOTE::
|
||||
|
||||
If the backend is killed during a configuration transaction it might be
|
||||
required to manually abort the transaction with `sudo gdnsd conf-abort`
|
||||
|
||||
Configured in [service:agent:gdnsd]
|
||||
"""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import string
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
import dns
|
||||
import dns.resolver
|
||||
from oslo_concurrency.processutils import ProcessExecutionError
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
|
||||
CFG_GROUP_NAME = 'backend:agent:gdnsd'
|
||||
LOG = logging.getLogger(__name__)
|
||||
# rootwrap requires a command name instead of full path
|
||||
GDNSD_DEFAULT_PATH = 'gdnsd'
|
||||
CONFDIR_PATH = '/etc/gdnsd'
|
||||
SOA_QUERY_TIMEOUT = 1
|
||||
ZONE_FILE_PERMISSIONS = 0o0644
|
||||
|
||||
|
||||
def filter_exceptions(fn):
|
||||
# Let Backend() exceptions pass through, log out every other exception
|
||||
# and re-raise it as Backend()
|
||||
def wrapper(*a, **kw):
|
||||
try:
|
||||
return fn(*a, **kw)
|
||||
except exceptions.Backend as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
LOG.error("Unhandled exception %s", e, exc_info=True)
|
||||
raise exceptions.Backend(e)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class GdnsdBackend(base.AgentBackend):
|
||||
__plugin_name__ = 'gdnsd'
|
||||
__backend_status__ = 'experimental'
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
"""Configure the backend"""
|
||||
super(GdnsdBackend, self).__init__(*a, **kw)
|
||||
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
self._gdnsd_cmd_name = cfg.CONF[CFG_GROUP_NAME].gdnsd_cmd_name
|
||||
LOG.info("gdnsd command: %r", self._gdnsd_cmd_name)
|
||||
self._confdir_path = cfg.CONF[CFG_GROUP_NAME].confdir_path
|
||||
self._zonedir_path = os.path.join(self._confdir_path, 'zones')
|
||||
LOG.info("gdnsd conf directory: %r", self._confdir_path)
|
||||
self._resolver = dns.resolver.Resolver(configure=False)
|
||||
self._resolver.timeout = SOA_QUERY_TIMEOUT
|
||||
self._resolver.lifetime = SOA_QUERY_TIMEOUT
|
||||
self._resolver.nameservers = [
|
||||
cfg.CONF[CFG_GROUP_NAME].query_destination
|
||||
]
|
||||
LOG.info("Resolvers: %r", self._resolver.nameservers)
|
||||
self._check_dirs(self._zonedir_path)
|
||||
|
||||
def start(self):
|
||||
"""Start the backend, check gdnsd configuration
|
||||
|
||||
:raises: exception.Backend on invalid configuration
|
||||
"""
|
||||
LOG.info("Started gdnsd backend")
|
||||
self._check_conf()
|
||||
|
||||
def _check_conf(self):
|
||||
"""Run gdnsd to check its configuration
|
||||
"""
|
||||
try:
|
||||
out, err = utils.execute(
|
||||
cfg.CONF[CFG_GROUP_NAME].gdnsd_cmd_name,
|
||||
'-D', '-x', 'checkconf', '-c', self._confdir_path,
|
||||
run_as_root=False,
|
||||
)
|
||||
except ProcessExecutionError as e:
|
||||
LOG.error("Command output: %(out)r Stderr: %(err)r",
|
||||
{
|
||||
'out': e.stdout,
|
||||
'err': e.stderr
|
||||
})
|
||||
raise exceptions.Backend("Configuration check failed")
|
||||
|
||||
def _check_dirs(self, *dirnames):
|
||||
"""Check if directories are writable
|
||||
"""
|
||||
for dn in dirnames:
|
||||
if not os.path.isdir(dn):
|
||||
raise exceptions.Backend("Missing directory %s" % dn)
|
||||
if not os.access(dn, os.W_OK):
|
||||
raise exceptions.Backend("Directory not writable: %s" % dn)
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
"""Query the local resolver for a zone
|
||||
Times out after SOA_QUERY_TIMEOUT
|
||||
"""
|
||||
LOG.debug("Finding %s", zone_name)
|
||||
try:
|
||||
rdata = self._resolver.query(
|
||||
zone_name, rdtype=dns.rdatatype.SOA)[0]
|
||||
return rdata.serial
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _generate_zone_filename(self, zone_name):
|
||||
"""Generate a filename for a zone file
|
||||
"/" is traslated into "@"
|
||||
Non-valid characters are translated into \\ NNN
|
||||
where NNN is a decimal integer in the range 0 - 255
|
||||
The filename is lowercase
|
||||
|
||||
:returns: valid filename (string)
|
||||
"""
|
||||
valid_chars = "-_.@%s%s" % (string.ascii_letters, string.digits)
|
||||
fname = zone_name.replace('/', '@').lower()
|
||||
fname = [c if c in valid_chars else "\03%d" % ord(c)
|
||||
for c in fname]
|
||||
return ''.join(fname)
|
||||
|
||||
def _write_zone_file(self, zone):
|
||||
"""Create or update a zone file atomically.
|
||||
The zone file is written to a unique temp file and then renamed
|
||||
"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
zone_base_fname = self._generate_zone_filename(zone_name)
|
||||
zone_fname = os.path.join(self._zonedir_path, zone_base_fname)
|
||||
try:
|
||||
# gdnsd ignores hidden files
|
||||
tmp_zone_fname = tempfile.mkstemp(
|
||||
prefix=".%s" % zone_base_fname,
|
||||
dir=self._zonedir_path,
|
||||
)[1]
|
||||
LOG.debug("Writing zone %r to %r and renaming it to %r",
|
||||
zone_name, tmp_zone_fname, zone_fname)
|
||||
zone.to_file(tmp_zone_fname)
|
||||
os.chmod(tmp_zone_fname, ZONE_FILE_PERMISSIONS)
|
||||
os.rename(tmp_zone_fname, zone_fname)
|
||||
finally:
|
||||
try:
|
||||
os.remove(tmp_zone_fname)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@filter_exceptions
|
||||
def create_zone(self, zone):
|
||||
"""Create a new Zone
|
||||
Do not raise exceptions if the zone already exists.
|
||||
|
||||
:param zone: zone to be created
|
||||
:type zone: raw pythondns Zone
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
# The zone might be already in place due to a race condition between
|
||||
# checking if the zone is there and creating it across different
|
||||
# greenlets
|
||||
self._write_zone_file(zone)
|
||||
|
||||
@filter_exceptions
|
||||
def update_zone(self, zone):
|
||||
"""Instruct Djbdns DNS to perform AXFR from MiniDNS
|
||||
|
||||
:param zone: zone to be created
|
||||
:type zone: raw pythondns Zone
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
self._write_zone_file(zone)
|
||||
|
||||
@filter_exceptions
|
||||
def delete_zone(self, zone_name):
|
||||
"""Delete a new Zone
|
||||
Do not raise exceptions if the zone does not exist.
|
||||
|
||||
:param zone_name: zone name
|
||||
:type zone_name: str
|
||||
:raises: exceptions.Backend on error
|
||||
"""
|
||||
zone_name = zone_name.rstrip('.')
|
||||
LOG.debug('Deleting Zone: %s', zone_name)
|
||||
zone_fn = self._generate_zone_filename(zone_name)
|
||||
zone_fn = os.path.join(self._zonedir_path, zone_fn)
|
||||
try:
|
||||
os.remove(zone_fn)
|
||||
LOG.debug('Deleted Zone: %s', zone_name)
|
||||
except OSError as e:
|
||||
if errno.ENOENT == e.errno:
|
||||
LOG.info("Zone datafile %s was already deleted", zone_fn)
|
||||
return
|
||||
raise
|
@ -1,215 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
backend.agent_backend.impl_knot2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Knot DNS agent backend
|
||||
|
||||
Create, update, delete zones locally on a Knot DNS resolver using the
|
||||
knotc utility.
|
||||
|
||||
Supported Knot versions: >= 2.1, < 3
|
||||
|
||||
`Knot DNS 2 User documentation <../../admin/backends/knot2_agent.html>`_
|
||||
|
||||
.. WARNING::
|
||||
|
||||
Untested, do not use in production.
|
||||
|
||||
.. NOTE::
|
||||
|
||||
If the backend is killed during a configuration transaction it might be
|
||||
required to manually abort the transaction with `sudo knotc conf-abort`
|
||||
|
||||
Configured in [service:agent:knot2]
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_concurrency.processutils import ProcessExecutionError
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
from designate import exceptions
|
||||
from designate.utils import execute
|
||||
|
||||
CFG_GROUP_NAME = 'backend:agent:knot2'
|
||||
LOG = logging.getLogger(__name__)
|
||||
# rootwrap requires a command name instead of full path
|
||||
KNOTC_DEFAULT_PATH = 'knotc'
|
||||
|
||||
# TODO(Federico) on zone creation and update, agent.handler unnecessarily
|
||||
# perfors AXFR from MiniDNS to the Agent to populate the `zone` argument
|
||||
# (needed by the Bind backend)
|
||||
|
||||
|
||||
class Knot2Backend(base.AgentBackend):
|
||||
__plugin_name__ = 'knot2'
|
||||
__backend_status__ = 'untested'
|
||||
_lock_name = 'knot2.lock'
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
"""Configure the backend"""
|
||||
super(Knot2Backend, self).__init__(*a, **kw)
|
||||
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
self._knotc_cmd_name = cfg.CONF[CFG_GROUP_NAME].knotc_cmd_name
|
||||
|
||||
def start(self):
|
||||
"""Start the backend"""
|
||||
LOG.info("Started knot2 backend")
|
||||
|
||||
def _execute_knotc(self, *knotc_args, **kw):
|
||||
"""Run the Knot client and check the output
|
||||
|
||||
:param expected_output: expected output (default: 'OK')
|
||||
:type expected_output: str
|
||||
:param expected_error: expected alternative output, will be \
|
||||
logged as info(). Default: not set.
|
||||
:type expected_error: str
|
||||
"""
|
||||
# Knotc returns "0" even on failure, we have to check for 'OK'
|
||||
# https://gitlab.labs.nic.cz/labs/knot/issues/456
|
||||
|
||||
LOG.debug("Executing knotc with %r", knotc_args)
|
||||
expected = kw.get('expected_output', 'OK')
|
||||
expected_alt = kw.get('expected_error', None)
|
||||
try:
|
||||
out, err = execute(self._knotc_cmd_name, *knotc_args)
|
||||
out = out.rstrip()
|
||||
LOG.debug("Command output: %r", out)
|
||||
if out != expected:
|
||||
if expected_alt is not None and out == expected_alt:
|
||||
LOG.info("Ignoring error: %r", out)
|
||||
else:
|
||||
raise ProcessExecutionError(stdout=out, stderr=err)
|
||||
|
||||
except ProcessExecutionError as e:
|
||||
LOG.error("Command output: %(out)r Stderr: %(err)r",
|
||||
{
|
||||
'out': e.stdout,
|
||||
'err': e.stderr
|
||||
})
|
||||
raise exceptions.Backend(e)
|
||||
|
||||
def _start_minidns_to_knot_axfr(self, zone_name):
|
||||
"""Instruct Knot to request an AXFR from MiniDNS. No need to lock
|
||||
or enter a configuration transaction.
|
||||
"""
|
||||
self._execute_knotc('zone-refresh', zone_name)
|
||||
|
||||
def _modify_zone(self, *knotc_args, **kw):
|
||||
"""Create or delete a zone while locking, and within a
|
||||
Knot transaction.
|
||||
Knot supports only one config transaction at a time.
|
||||
|
||||
:raises: exceptions.Backend
|
||||
"""
|
||||
with lockutils.lock(self._lock_name):
|
||||
self._execute_knotc('conf-begin')
|
||||
try:
|
||||
self._execute_knotc(*knotc_args, **kw)
|
||||
# conf-diff can be used for debugging
|
||||
# self._execute_knotc('conf-diff')
|
||||
except Exception as e:
|
||||
self._execute_knotc('conf-abort')
|
||||
LOG.info("Zone change aborted: %r", e)
|
||||
raise
|
||||
else:
|
||||
self._execute_knotc('conf-commit')
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
"""Get serial from a zone by running knotc
|
||||
|
||||
:returns: serial (int or None)
|
||||
:raises: exceptions.Backend
|
||||
"""
|
||||
zone_name = zone_name.rstrip('.')
|
||||
LOG.debug("Finding %s", zone_name)
|
||||
# Output example:
|
||||
# [530336536.com.] type: slave | serial: 0 | next-event: idle |
|
||||
# auto-dnssec: disabled]
|
||||
try:
|
||||
out, err = execute(self._knotc_cmd_name, 'zone-status', zone_name)
|
||||
except ProcessExecutionError as e:
|
||||
if 'no such zone' in e.stdout:
|
||||
# Zone not found
|
||||
return None
|
||||
|
||||
LOG.error("Command output: %(out)r Stderr: %(err)r",
|
||||
{
|
||||
'out': e.stdout,
|
||||
'err': e.stderr
|
||||
})
|
||||
raise exceptions.Backend(e)
|
||||
|
||||
try:
|
||||
serial = out.split('|')[1].split()[1]
|
||||
return int(serial)
|
||||
except Exception:
|
||||
LOG.error("Unable to parse knotc output: %r", out)
|
||||
raise exceptions.Backend("Unexpected knotc zone-status output")
|
||||
|
||||
def create_zone(self, zone):
|
||||
"""Create a new Zone by executing knotc
|
||||
Do not raise exceptions if the zone already exists.
|
||||
|
||||
:param zone: zone to be created
|
||||
:type zone: raw pythondns Zone
|
||||
"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
LOG.debug("Creating %s", zone_name)
|
||||
# The zone might be already in place due to a race condition between
|
||||
# checking if the zone is there and creating it across different
|
||||
# greenlets
|
||||
self._modify_zone('conf-set', 'zone[%s]' % zone_name,
|
||||
expected_error='duplicate identifier')
|
||||
|
||||
LOG.debug("Triggering initial AXFR from MiniDNS to Knot for %s",
|
||||
zone_name)
|
||||
self._start_minidns_to_knot_axfr(zone_name)
|
||||
|
||||
def update_zone(self, zone):
|
||||
"""Instruct Knot DNS to perform AXFR from MiniDNS
|
||||
|
||||
:param zone: zone to be created
|
||||
:type zone: raw pythondns Zone
|
||||
"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
LOG.debug("Triggering AXFR from MiniDNS to Knot for %s", zone_name)
|
||||
self._start_minidns_to_knot_axfr(zone_name)
|
||||
|
||||
def delete_zone(self, zone_name):
|
||||
"""Delete a new Zone by executing knotc
|
||||
Do not raise exceptions if the zone does not exist.
|
||||
|
||||
:param zone_name: zone name
|
||||
:type zone_name: str
|
||||
"""
|
||||
LOG.debug('Delete Zone: %s' % zone_name)
|
||||
self._modify_zone('conf-unset', 'zone[%s]' % zone_name,
|
||||
expected_error='invalid identifier')
|
@ -1,111 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Author: Alin Balutoiu <abalutoiu@cloudbasesolutions.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 warnings
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions as os_win_exc
|
||||
from os_win import utilsfactory
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from designate.backend.agent_backend import base
|
||||
from designate import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MSDNSBackend(base.AgentBackend):
|
||||
|
||||
__plugin_name__ = 'msdns'
|
||||
__backend_status__ = 'experimental'
|
||||
|
||||
def __init__(self, agent_service):
|
||||
"""Configure the backend"""
|
||||
super(MSDNSBackend, self).__init__(agent_service)
|
||||
|
||||
warning_msg = ('The designate agent framework and backend driver "{}" '
|
||||
'are deprecated as of the Antelope (2023.1) release '
|
||||
'and will be removed in the "C" '
|
||||
'release.'.format(self.__plugin_name__))
|
||||
warnings.warn(warning_msg, DeprecationWarning)
|
||||
|
||||
self._dnsutils = utilsfactory.get_dnsutils()
|
||||
|
||||
masters = cfg.CONF['service:agent'].masters
|
||||
if not masters:
|
||||
raise exceptions.Backend("Missing agent AXFR masters")
|
||||
# Only ip addresses are needed
|
||||
self._masters = [ns.split(":")[0] for ns in masters]
|
||||
|
||||
LOG.info("AXFR masters: %r", self._masters)
|
||||
|
||||
def start(self):
|
||||
"""Start the backend"""
|
||||
LOG.info("Started msdns backend")
|
||||
|
||||
def find_zone_serial(self, zone_name):
|
||||
"""Return the zone's serial"""
|
||||
zone_name = zone_name.rstrip(".")
|
||||
LOG.debug("Finding zone: %s", zone_name)
|
||||
try:
|
||||
return self._dnsutils.get_zone_serial(zone_name)
|
||||
except os_win_exc.DNSZoneNotFound:
|
||||
# Return None if the zone was not found
|
||||
return None
|
||||
|
||||
def create_zone(self, zone):
|
||||
"""Create a new DNS Zone"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
LOG.debug("Creating zone: %s", zone_name)
|
||||
try:
|
||||
self._dnsutils.zone_create(
|
||||
zone_name=zone_name,
|
||||
zone_type=constants.DNS_ZONE_TYPE_SECONDARY,
|
||||
ds_integrated=False,
|
||||
ip_addrs=self._masters)
|
||||
except os_win_exc.DNSZoneAlreadyExists:
|
||||
# Zone already exists, check its properties to see if the
|
||||
# existing zone is identical to the requested one
|
||||
zone_properties = self._dnsutils.get_zone_properties(zone_name)
|
||||
|
||||
identical_zone_exists = (
|
||||
zone_properties['zone_type'] == (
|
||||
constants.DNS_ZONE_TYPE_SECONDARY) and
|
||||
zone_properties['ds_integrated'] is False and
|
||||
set(zone_properties['master_servers']) == set(self._masters))
|
||||
|
||||
if not identical_zone_exists:
|
||||
raise
|
||||
|
||||
def update_zone(self, zone):
|
||||
"""Instruct MSDNS to request an AXFR from MiniDNS.
|
||||
"""
|
||||
zone_name = zone.origin.to_text(omit_final_dot=True)
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
LOG.debug("Updating zone: %s", zone_name)
|
||||
self._dnsutils.zone_update(zone_name)
|
||||
|
||||
def delete_zone(self, zone_name):
|
||||
"""Delete a DNS Zone
|
||||
Do not raise exception if the zone does not exist.
|
||||
"""
|
||||
LOG.debug('Deleting zone: %s' % zone_name)
|
||||
zone_name = zone_name.rstrip(".")
|
||||
self._dnsutils.zone_delete(zone_name)
|
@ -1,90 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 dns
|
||||
|
||||
from debtcollector import removals
|
||||
|
||||
"""
|
||||
backend.private_codes
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
Private DNS opcodes, classes, RR codes for communication between the
|
||||
agent and its backends
|
||||
"""
|
||||
|
||||
# Command and Control OPCODE
|
||||
CC = 14
|
||||
|
||||
# Private DNS CLASS Uses
|
||||
CLASSCC = 65280
|
||||
|
||||
# Private RR Code Uses
|
||||
SUCCESS = 65280
|
||||
FAILURE = 65281
|
||||
CREATE = 65282
|
||||
DELETE = 65283
|
||||
|
||||
# TODO(johnsom) Remove this after the agents framework is removed or the
|
||||
# protocol has been updated to not use an unassigned opcode(14).
|
||||
#
|
||||
# This is an Opcode Enum class that includes the unassigned[1][2]
|
||||
# opcode 14 used in the Designate agent framework until the agent framework
|
||||
# can be removed or fixed.
|
||||
# [1] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.2
|
||||
# [2] https://www.iana.org/assignments/dns-parameters/
|
||||
# dns-parameters.xhtml#dns-parameters-5
|
||||
#
|
||||
# Based on dns.opcode.Opcode:
|
||||
#
|
||||
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||||
# Copyright (C) 2001-2017 Nominum, Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software and its
|
||||
# documentation for any purpose with or without fee is hereby granted,
|
||||
# provided that the above copyright notice and this permission notice
|
||||
# appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||||
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||||
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
|
||||
@removals.removed_class("OpcodeWith14")
|
||||
class OpcodeWith14(dns.enum.IntEnum):
|
||||
#: Query
|
||||
QUERY = 0
|
||||
#: Inverse Query (historical)
|
||||
IQUERY = 1
|
||||
#: Server Status (unspecified and unimplemented anywhere)
|
||||
STATUS = 2
|
||||
#: Notify
|
||||
NOTIFY = 4
|
||||
#: Dynamic Update
|
||||
UPDATE = 5
|
||||
|
||||
# Unassigned, but used by Designate for command/control in the agents
|
||||
UNASSIGNED14 = 14
|
||||
|
||||
@classmethod
|
||||
def _maximum(cls):
|
||||
return 15
|
||||
|
||||
@classmethod
|
||||
def _unknown_exception_class(cls):
|
||||
return dns.opcode.UnknownOpcode
|
@ -1,47 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 sys
|
||||
import warnings
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_reports import guru_meditation_report as gmr
|
||||
|
||||
from designate.agent import service as agent_service
|
||||
import designate.conf
|
||||
from designate import heartbeat_emitter
|
||||
from designate import service
|
||||
from designate import utils
|
||||
from designate import version
|
||||
|
||||
|
||||
CONF = designate.conf.CONF
|
||||
CONF.import_opt('workers', 'designate.agent', group='service:agent')
|
||||
|
||||
|
||||
def main():
|
||||
utils.read_config('designate', sys.argv)
|
||||
logging.setup(CONF, 'designate')
|
||||
gmr.TextGuruMeditation.setup_autorun(version)
|
||||
|
||||
warnings.warn('The designate agent process is deprecated as of the '
|
||||
'Antelope (2023.1) release and will be removed in the '
|
||||
'"C" release.', DeprecationWarning)
|
||||
|
||||
server = agent_service.Service()
|
||||
heartbeat = heartbeat_emitter.get_heartbeat_emitter(server.service_name)
|
||||
service.serve(server, workers=CONF['service:agent'].workers)
|
||||
heartbeat.start()
|
||||
service.wait()
|
@ -13,22 +13,15 @@
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
from designate.conf import agent
|
||||
from designate.conf import api
|
||||
from designate.conf import base # noqa
|
||||
from designate.conf import bind9
|
||||
from designate.conf import central
|
||||
from designate.conf import coordination
|
||||
from designate.conf import denominator
|
||||
from designate.conf import djbdns
|
||||
from designate.conf import dynect
|
||||
from designate.conf import gdnsd
|
||||
from designate.conf import heartbeat_emitter
|
||||
from designate.conf import infoblox
|
||||
from designate.conf import keystone
|
||||
from designate.conf import knot2
|
||||
from designate.conf import mdns
|
||||
from designate.conf import msdns
|
||||
from designate.conf import network_api
|
||||
from designate.conf import producer
|
||||
from designate.conf import proxy
|
||||
@ -39,21 +32,14 @@ from designate.conf import worker
|
||||
CONF = cfg.CONF
|
||||
|
||||
base.register_opts(CONF)
|
||||
agent.register_opts(CONF)
|
||||
api.register_opts(CONF)
|
||||
bind9.register_opts(CONF)
|
||||
central.register_opts(CONF)
|
||||
coordination.register_opts(CONF)
|
||||
denominator.register_opts(CONF)
|
||||
djbdns.register_opts(CONF)
|
||||
dynect.register_opts(CONF)
|
||||
gdnsd.register_opts(CONF)
|
||||
heartbeat_emitter.register_opts(CONF)
|
||||
infoblox.register_opts(CONF)
|
||||
keystone.register_opts(CONF)
|
||||
knot2.register_opts(CONF)
|
||||
mdns.register_opts(CONF)
|
||||
msdns.register_opts(CONF)
|
||||
network_api.register_opts(CONF)
|
||||
producer.register_opts(CONF)
|
||||
proxy.register_opts(CONF)
|
||||
|
@ -1,89 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
DEFAULT_AGENT_PORT = 5358
|
||||
|
||||
AGENT_GROUP = cfg.OptGroup(
|
||||
name='service:agent',
|
||||
title="Configuration for the Agent Service"
|
||||
)
|
||||
|
||||
AGENT_OPTS = [
|
||||
cfg.IntOpt('workers',
|
||||
help='Number of agent worker processes to spawn',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.IntOpt('threads', default=1000,
|
||||
help='Number of agent greenthreads to spawn',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.ListOpt('listen',
|
||||
default=['0.0.0.0:%d' % DEFAULT_AGENT_PORT],
|
||||
help='Agent host:port pairs to listen on',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.IntOpt('tcp_backlog', default=100,
|
||||
help='The Agent TCP Backlog',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.FloatOpt('tcp_recv_timeout', default=0.5,
|
||||
help='Agent TCP Receive Timeout',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.ListOpt('allow_notify', default=[],
|
||||
help='List of IP addresses allowed to NOTIFY The Agent',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.ListOpt('masters', default=[],
|
||||
help='List of masters for the Agent, format ip:port',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('backend_driver', default='bind9',
|
||||
help='The backend driver to use, e.g. bind9, djbdns, knot2',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('transfer_source',
|
||||
help='An IP address to be used to fetch zones transferred in',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.FloatOpt('notify_delay', default=0.0,
|
||||
help='Delay after a NOTIFY arrives for a zone that the Agent '
|
||||
'will pause and drop subsequent NOTIFYs for that zone',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(AGENT_GROUP)
|
||||
conf.register_opts(AGENT_OPTS, group=AGENT_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
AGENT_GROUP: AGENT_OPTS
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
BIND9_GROUP = cfg.OptGroup(
|
||||
name='backend:agent:bind9',
|
||||
title="Configuration for bind9 backend"
|
||||
)
|
||||
|
||||
BINS9_OPTS = [
|
||||
cfg.StrOpt('rndc_host', default='127.0.0.1', help='RNDC Host',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.IntOpt('rndc_port', default=953, help='RNDC Port',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('rndc_config_file',
|
||||
help='RNDC Config File',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('rndc_key_file', help='RNDC Key File',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.IntOpt('rndc_timeout', default=0, min=0, help='RNDC command timeout',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('zone_file_path', default='$state_path/zones',
|
||||
help='Path where zone files are stored',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('query_destination', default='127.0.0.1',
|
||||
help='Host to query when finding zones',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(BIND9_GROUP)
|
||||
conf.register_opts(BINS9_OPTS, group=BIND9_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
BIND9_GROUP: BINS9_OPTS,
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
# Copyright 2015 Dyn Inc.
|
||||
#
|
||||
# Author: Yasha Bubnov <ybubnov@dyn.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
DENOMINATOR_GROUP = cfg.OptGroup(
|
||||
name='backend:agent:denominator',
|
||||
title='Backend options for Denominator',
|
||||
)
|
||||
|
||||
DENOMINATOR_OPTS = [
|
||||
cfg.StrOpt('name', default='fake',
|
||||
help='Name of the affected provider',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('config_file', default='/etc/denominator.conf',
|
||||
help='Path to Denominator configuration file',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(DENOMINATOR_GROUP)
|
||||
conf.register_opts(DENOMINATOR_OPTS, group=DENOMINATOR_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
DENOMINATOR_GROUP: DENOMINATOR_OPTS,
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
DJBDNS_GROUP = cfg.OptGroup(
|
||||
name='backend:agent:djbdns',
|
||||
title="Configuration for Djbdns backend"
|
||||
)
|
||||
|
||||
DJDNS_OPTS = [
|
||||
cfg.StrOpt(
|
||||
'tcpclient_cmd_name',
|
||||
help='tcpclient executable path or rootwrap command name',
|
||||
default='tcpclient', deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'axfr_get_cmd_name',
|
||||
help='axfr-get executable path or rootwrap command name',
|
||||
default='axfr-get', deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'tinydns_data_cmd_name',
|
||||
help='tinydns-data executable path or rootwrap command name',
|
||||
default='tinydns-data', deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'tinydns_datadir',
|
||||
help='TinyDNS data directory',
|
||||
default='/var/lib/djbdns', deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'
|
||||
),
|
||||
cfg.StrOpt('query_destination', default='127.0.0.1',
|
||||
help='Host to query when finding zones',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(DJBDNS_GROUP)
|
||||
conf.register_opts(DJDNS_OPTS, group=DJBDNS_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
DJBDNS_GROUP: DJDNS_OPTS,
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
GDNSD_GROUP = cfg.OptGroup(
|
||||
name='backend:agent:gdnsd',
|
||||
title="Configuration for gdnsd backend"
|
||||
)
|
||||
GDNSD_OPTS = [
|
||||
cfg.StrOpt('gdnsd_cmd_name',
|
||||
help='gdnsd executable path or rootwrap command name',
|
||||
default='gdnsd',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('confdir_path',
|
||||
help='gdnsd configuration directory path',
|
||||
default='/etc/gdnsd',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('query_destination', default='127.0.0.1',
|
||||
help='Host to query when finding zones',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(GDNSD_GROUP)
|
||||
conf.register_opts(GDNSD_OPTS, group=GDNSD_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
GDNSD_GROUP: GDNSD_OPTS,
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
KNOT2_GROUP = cfg.OptGroup(
|
||||
name='backend:agent:knot2',
|
||||
title="Configuration for Knot2 backend"
|
||||
)
|
||||
|
||||
KNOT2_OPTS = [
|
||||
cfg.StrOpt('knotc_cmd_name',
|
||||
help='knotc executable path or rootwrap command name',
|
||||
default='knotc',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
cfg.StrOpt('query_destination', default='127.0.0.1',
|
||||
help='Host to query when finding zones',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='Antelope(2023.1)',
|
||||
deprecated_reason='The agent framework is deprecated.'),
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(KNOT2_GROUP)
|
||||
conf.register_opts(KNOT2_OPTS, group=KNOT2_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
KNOT2_GROUP: KNOT2_OPTS,
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Author: Alin Balutoiu <abalutoiu@cloudbasesolutions.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
|
||||
MSDNS_GROUP = cfg.OptGroup(
|
||||
name='backend:agent:msdns',
|
||||
title="Configuration for Microsoft DNS Server"
|
||||
)
|
||||
MSDNS_OPTS = [
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_group(MSDNS_GROUP)
|
||||
conf.register_opts(MSDNS_OPTS, group=MSDNS_GROUP)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
MSDNS_GROUP: MSDNS_OPTS,
|
||||
}
|
@ -13,8 +13,6 @@
|
||||
# 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 dns.exception
|
||||
import dns.message
|
||||
import dns.opcode
|
||||
@ -26,7 +24,6 @@ from oslo_log import log as logging
|
||||
|
||||
import designate.conf
|
||||
from designate import context
|
||||
from designate import dnsutils
|
||||
from designate import exceptions
|
||||
|
||||
CONF = designate.conf.CONF
|
||||
@ -176,39 +173,3 @@ class TsigInfoMiddleware(DNSMiddleware):
|
||||
return self._build_error_response()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class LimitNotifyMiddleware(DNSMiddleware):
|
||||
"""Middleware that rate limits NOTIFYs to the Agent"""
|
||||
|
||||
def __init__(self, application):
|
||||
super(LimitNotifyMiddleware, self).__init__(application)
|
||||
|
||||
self.delay = CONF['service:agent'].notify_delay
|
||||
self.locker = dnsutils.ZoneLock(self.delay)
|
||||
|
||||
def process_request(self, request):
|
||||
opcode = request.opcode()
|
||||
if opcode != dns.opcode.NOTIFY:
|
||||
return None
|
||||
|
||||
zone_name = request.question[0].name.to_text()
|
||||
if isinstance(zone_name, bytes):
|
||||
zone_name = zone_name.decode('utf-8')
|
||||
|
||||
if self.locker.acquire(zone_name):
|
||||
time.sleep(self.delay)
|
||||
self.locker.release(zone_name)
|
||||
return None
|
||||
else:
|
||||
LOG.debug(
|
||||
'Threw away NOTIFY for %(zone)s, already '
|
||||
'working on an update.',
|
||||
{
|
||||
'zone': zone_name
|
||||
}
|
||||
)
|
||||
response = dns.message.make_response(request)
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
return (response,)
|
||||
|
@ -13,14 +13,11 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
import dns
|
||||
import dns.query
|
||||
import dns.tsigkeyring
|
||||
from oslo_config import cfg
|
||||
|
||||
from designate import dnsmiddleware
|
||||
from designate import dnsutils
|
||||
from designate import exceptions
|
||||
from designate import objects
|
||||
@ -194,47 +191,3 @@ class TestUtils(designate.tests.TestCase):
|
||||
self.assertTrue(lock.acquire('example3.com.'))
|
||||
lock.release('example3.com.')
|
||||
self.assertTrue(lock.acquire('example3.com.'))
|
||||
|
||||
def test_limit_notify_middleware(self):
|
||||
self.CONF.set_override('notify_delay', 0.1, 'service:agent')
|
||||
|
||||
# Initialize the middlware
|
||||
placeholder_app = None
|
||||
middleware = dnsmiddleware.LimitNotifyMiddleware(placeholder_app)
|
||||
|
||||
# Prepare a NOTIFY
|
||||
zone_name = 'example.com.'
|
||||
notify = dns.message.make_query(zone_name, dns.rdatatype.SOA)
|
||||
notify.flags = 0
|
||||
notify.set_opcode(dns.opcode.NOTIFY)
|
||||
notify.flags |= dns.flags.AA
|
||||
|
||||
# Send the NOTIFY through the middleware
|
||||
# No problem, middleware should return None to pass it on
|
||||
self.assertIsNone(middleware.process_request(notify))
|
||||
|
||||
@mock.patch('designate.dnsutils.ZoneLock.acquire', return_value=False)
|
||||
def test_limit_notify_middleware_no_acquire(self, mock_acquire):
|
||||
self.CONF.set_override('notify_delay', 0.1, 'service:agent')
|
||||
|
||||
# Initialize the middlware
|
||||
placeholder_app = None
|
||||
middleware = dnsmiddleware.LimitNotifyMiddleware(placeholder_app)
|
||||
|
||||
# Prepare a NOTIFY
|
||||
zone_name = 'example.com.'
|
||||
notify = dns.message.make_query(zone_name, dns.rdatatype.SOA)
|
||||
notify.flags = 0
|
||||
notify.set_opcode(dns.opcode.NOTIFY)
|
||||
notify.flags |= dns.flags.AA
|
||||
|
||||
# Make a response object to match the middleware's return
|
||||
response = dns.message.make_response(notify)
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
|
||||
# Send the NOTIFY through the middleware
|
||||
# Lock can't be acquired, a NOTIFY is already being worked on
|
||||
# so just return what would have come back for a successful NOTIFY
|
||||
# This needs to be a one item tuple for the serialization middleware
|
||||
self.assertEqual(middleware.process_request(notify), (response,))
|
||||
|
@ -1,27 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 dns.zone
|
||||
|
||||
|
||||
def create_dnspy_zone(name):
|
||||
if not name.endswith('.'):
|
||||
name = name + '.'
|
||||
zone_text = (
|
||||
'$ORIGIN %(name)s\n%(name)s 3600 IN SOA %(ns)s email.email.com. '
|
||||
'1421777854 3600 600 86400 3600\n%(name)s 3600 IN NS %(ns)s\n'
|
||||
)
|
||||
|
||||
return dns.zone.from_text(
|
||||
zone_text % {'name': name, 'ns': 'ns1.designate.com'},
|
||||
check_origin=False
|
||||
)
|
@ -1,131 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
import dns.resolver
|
||||
|
||||
from designate.backend.agent_backend import impl_bind9
|
||||
from designate import exceptions
|
||||
import designate.tests
|
||||
from designate.tests.unit.agent import backends
|
||||
from designate import utils
|
||||
|
||||
|
||||
class Bind9AgentBackendTestCase(designate.tests.TestCase):
|
||||
def setUp(self):
|
||||
super(Bind9AgentBackendTestCase, self).setUp()
|
||||
|
||||
self.CONF.set_override('listen', ['0.0.0.0:0'], 'service:agent')
|
||||
|
||||
self.backend = impl_bind9.Bind9Backend('foo')
|
||||
|
||||
def test_start_backend(self):
|
||||
self.backend.start()
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
@mock.patch.object(dns.resolver.Resolver, 'query')
|
||||
def test_find_zone_serial(self, mock_query):
|
||||
self.assertIsNotNone(self.backend.find_zone_serial('example.org.'))
|
||||
|
||||
@mock.patch.object(dns.resolver.Resolver, 'query')
|
||||
def test_find_zone_serial_query_raises(self, mock_query):
|
||||
mock_query.side_effect = Exception()
|
||||
self.assertIsNone(self.backend.find_zone_serial('example.org.'))
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
@mock.patch('designate.backend.agent_backend.impl_bind9.Bind9Backend'
|
||||
'._sync_zone')
|
||||
def test_create_zone(self, mock_execute, mock_sync_zone):
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
@mock.patch('designate.backend.agent_backend.impl_bind9.Bind9Backend'
|
||||
'._sync_zone')
|
||||
def test_update_zone(self, mock_execute, mock_sync_zone):
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
self.backend.update_zone(zone)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
@mock.patch('designate.backend.agent_backend.impl_bind9.Bind9Backend'
|
||||
'._sync_zone')
|
||||
def test_delete_zone(self, mock_execute, mock_sync_zone):
|
||||
self.backend.delete_zone('example.org.')
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
def test_execute_rndc(self, mock_execute):
|
||||
self.CONF.set_override(
|
||||
'rndc_config_file', 'config_file', 'backend:agent:bind9'
|
||||
)
|
||||
self.CONF.set_override(
|
||||
'rndc_key_file', 'key_file', 'backend:agent:bind9'
|
||||
)
|
||||
|
||||
self.backend._execute_rndc(self.backend._rndc_base())
|
||||
|
||||
mock_execute.assert_called_once_with(
|
||||
'rndc', '-s', '127.0.0.1', '-p', '953',
|
||||
'-c', 'config_file', '-k', 'key_file'
|
||||
)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
def test_execute_rndc_raises(self, mock_execute):
|
||||
mock_execute.side_effect = utils.processutils.ProcessExecutionError()
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend._execute_rndc, self.backend._rndc_base()
|
||||
)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
@mock.patch.object(dns.zone.Zone, 'to_file')
|
||||
def test_sync_zone(self, mock_to_file, mock_execute):
|
||||
FAKE_STATE_PATH = '/tmp/fake/state/path'
|
||||
self.CONF.set_override('state_path', FAKE_STATE_PATH)
|
||||
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
|
||||
self.backend._sync_zone(zone)
|
||||
|
||||
mock_to_file.assert_called_once_with(
|
||||
FAKE_STATE_PATH + '/zones/example.org.zone', relativize=False
|
||||
)
|
||||
|
||||
mock_execute.assert_called_once_with(
|
||||
'rndc', '-s', '127.0.0.1', '-p', '953', 'reload', 'example.org'
|
||||
)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
@mock.patch.object(dns.zone.Zone, 'to_file')
|
||||
def test_sync_zone_with_new_zone(self, mock_to_file, mock_execute):
|
||||
FAKE_STATE_PATH = '/tmp/fake/state/path'
|
||||
self.CONF.set_override('state_path', FAKE_STATE_PATH)
|
||||
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
|
||||
self.backend._sync_zone(zone, new_zone_flag=True)
|
||||
|
||||
mock_to_file.assert_called_once_with(
|
||||
FAKE_STATE_PATH + '/zones/example.org.zone', relativize=False
|
||||
)
|
||||
|
||||
mock_execute.assert_called_once_with(
|
||||
'rndc', '-s', '127.0.0.1', '-p', '953', 'addzone',
|
||||
'example.org { type master; '
|
||||
'file "' + FAKE_STATE_PATH + '/zones/example.org.zone"; };'
|
||||
)
|
@ -1,168 +0,0 @@
|
||||
# Copyright 2015 Dyn Inc.
|
||||
#
|
||||
# Author: Yasha Bubnov <ybubnov@dyn.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from designate.backend.agent_backend import impl_denominator
|
||||
from designate import exceptions
|
||||
from designate import tests
|
||||
from designate.tests.unit.agent import backends
|
||||
from designate import utils
|
||||
|
||||
|
||||
class DenominatorAgentBackendTestCase(tests.TestCase):
|
||||
def setUp(self):
|
||||
super(DenominatorAgentBackendTestCase, self).setUp()
|
||||
|
||||
self.CONF.set_override('listen', ['0.0.0.0:0'], 'service:agent')
|
||||
|
||||
self.backend = impl_denominator.DenominatorBackend('foo')
|
||||
|
||||
def test_start_backend(self):
|
||||
self.backend.start()
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
@mock.patch('designate.utils.execute', return_value=(
|
||||
'example.org SOA 86400 ns1.designate.com. '
|
||||
'hostmaster@example.org. 475 3600 600 604800 1800', None))
|
||||
def test_find_zone_serial(self, mock_execute):
|
||||
serial = self.backend.find_zone_serial('example.org.')
|
||||
|
||||
# Ensure returned right serial number
|
||||
self.assertEqual(475, serial)
|
||||
|
||||
# Ensure called "denominator zone add"
|
||||
self.assertIn('record', mock_execute.call_args[0])
|
||||
self.assertIn('get', mock_execute.call_args[0])
|
||||
|
||||
@mock.patch('designate.utils.execute', return_value=('', None))
|
||||
def test_find_zone_serial_fail(self, mock_execute):
|
||||
serial = self.backend.find_zone_serial('example.org.')
|
||||
self.assertIsNone(serial)
|
||||
|
||||
@mock.patch('designate.utils.execute', return_value=(None, None))
|
||||
def test_create_zone(self, mock_execute):
|
||||
zone = backends.create_dnspy_zone('example.org.')
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
# Ensure denominator called for each record (except SOA)
|
||||
# plus one to update zone data
|
||||
self.assertEqual(
|
||||
mock_execute.call_count, len(list(zone.iterate_rdatas()))
|
||||
)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
def test_update_zone(self, mock_execute):
|
||||
# Output from 'designate record list' command
|
||||
records = ('example.org SOA 86400 ns1.designate.com. '
|
||||
'hostmaster@example.org. 475 3600 600 604800 1800\n'
|
||||
'example.org NS 86400 ns1.designator.net.\n'
|
||||
'example.org NS 86400 ns2.designator.net.\n'
|
||||
'example.org MX 86400 10 mx1.designator.net.')
|
||||
|
||||
# That should force update_zone to delete A and AAAA records
|
||||
# from the zone and create a new MX record.
|
||||
mock_execute.return_value = (records, None)
|
||||
|
||||
zone = backends.create_dnspy_zone('example.org.')
|
||||
self.backend.update_zone(zone)
|
||||
|
||||
# Ensure denominator called to:
|
||||
# *update zone info
|
||||
# *fetch list of zone records
|
||||
# *delete one MX record
|
||||
# *replace one NS record
|
||||
# *create 0 records
|
||||
# total: 4 calls
|
||||
|
||||
self.assertEqual(4, mock_execute.call_count)
|
||||
|
||||
methods = ['update_zone',
|
||||
'get_records',
|
||||
'create_record', 'update_record', 'delete_record']
|
||||
for method in methods:
|
||||
setattr(self.backend.denominator, method, mock.Mock(
|
||||
return_value=records))
|
||||
|
||||
self.backend.update_zone(zone)
|
||||
self.assertEqual(1, self.backend.denominator.update_zone.call_count)
|
||||
self.assertEqual(1, self.backend.denominator.get_records.call_count)
|
||||
self.assertEqual(0, self.backend.denominator.create_record.call_count)
|
||||
self.assertEqual(1, self.backend.denominator.update_record.call_count)
|
||||
self.assertEqual(1, self.backend.denominator.delete_record.call_count)
|
||||
|
||||
@mock.patch('designate.utils.execute', return_value=(None, None))
|
||||
def test_delete_zone(self, mock_execute):
|
||||
self.backend.delete_zone('example.org.')
|
||||
|
||||
# Ensure called 'denominator zone delete'
|
||||
self.assertEqual(1, mock_execute.call_count)
|
||||
self.assertIn('zone', mock_execute.call_args[0])
|
||||
self.assertIn('delete', mock_execute.call_args[0])
|
||||
|
||||
|
||||
class DenominatorAgentBaseTestCase(tests.TestCase):
|
||||
def setUp(self):
|
||||
super(DenominatorAgentBaseTestCase, self).setUp()
|
||||
|
||||
self.backend = impl_denominator.Denominator(
|
||||
cfg.CONF['backend:agent:denominator']
|
||||
)
|
||||
|
||||
def test_base(self):
|
||||
self.assertEqual(
|
||||
['denominator', '-q', '-n', 'fake', '-C', '/etc/denominator.conf'],
|
||||
|
||||
self.backend._base()
|
||||
)
|
||||
|
||||
def test_base_without_config_file(self):
|
||||
self.CONF.set_override(
|
||||
'config_file', '', 'backend:agent:denominator'
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
['denominator', '-q', '-n', 'fake'],
|
||||
self.backend._base()
|
||||
)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
def test_execute(self, mock_execute):
|
||||
mock_execute.return_value = ('stdout', None,)
|
||||
|
||||
self.assertEqual(
|
||||
'stdout',
|
||||
self.backend._execute(
|
||||
['record', '-z', 'example.org', 'add'], {'name': 'example.org'}
|
||||
)
|
||||
)
|
||||
|
||||
mock_execute.assert_called_once_with(
|
||||
'denominator', '-q', '-n', 'fake', '-C', '/etc/denominator.conf',
|
||||
'record', '-z', 'example.org', 'add', '--name', 'example.org'
|
||||
)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
def test_execute_raises(self, mock_execute):
|
||||
mock_execute.side_effect = utils.processutils.ProcessExecutionError()
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.DesignateException,
|
||||
self.backend._execute, ['record', '-z', 'example.org', 'add'], {}
|
||||
)
|
@ -1,125 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
from designate.backend.agent_backend import impl_djbdns
|
||||
from designate import exceptions
|
||||
import designate.tests
|
||||
from designate.tests.unit.agent import backends
|
||||
|
||||
|
||||
class DjbdnsAgentBackendTestCase(designate.tests.TestCase):
|
||||
@mock.patch.object(impl_djbdns.DjbdnsBackend, '_check_dirs')
|
||||
def setUp(self, mock_check_dirs):
|
||||
super(DjbdnsAgentBackendTestCase, self).setUp()
|
||||
|
||||
self.CONF.set_override('masters', ['127.0.0.1:5354'], 'service:agent')
|
||||
|
||||
self.backend = impl_djbdns.DjbdnsBackend('foo')
|
||||
|
||||
def test_start_backend(self):
|
||||
self.backend.start()
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
def test_init(self):
|
||||
self.assertTrue(hasattr(self.backend, '_resolver'))
|
||||
self.assertEqual(1, self.backend._resolver.timeout)
|
||||
self.assertEqual(1, self.backend._resolver.lifetime)
|
||||
self.assertEqual(['127.0.0.1'], self.backend._resolver.nameservers)
|
||||
self.assertEqual(
|
||||
'/var/lib/djbdns/root/data.cdb',
|
||||
self.backend._tinydns_cdb_filename
|
||||
)
|
||||
self.assertEqual(
|
||||
'/var/lib/djbdns/datafiles',
|
||||
self.backend._datafiles_dir
|
||||
|
||||
)
|
||||
self.assertEqual(
|
||||
'/var/lib/djbdns/datafiles/%s.zonedata',
|
||||
self.backend._datafiles_path_tpl
|
||||
)
|
||||
self.assertEqual([('127.0.0.1', 5354)], self.backend._masters)
|
||||
|
||||
@mock.patch.object(impl_djbdns.DjbdnsBackend, '_check_dirs')
|
||||
def test_init_no_masters(self, mock_check_dirs):
|
||||
self.CONF.set_override('masters', [], 'service:agent')
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exceptions.Backend,
|
||||
'Missing agent AXFR masters',
|
||||
impl_djbdns.DjbdnsBackend, 'foo'
|
||||
)
|
||||
|
||||
def test_find_zone_serial(self):
|
||||
class Data(object):
|
||||
serial = 3
|
||||
|
||||
self.backend._resolver = mock.Mock()
|
||||
self.backend._resolver.query.return_value = [Data(), ]
|
||||
serial = self.backend.find_zone_serial('example.com')
|
||||
self.assertEqual(3, serial)
|
||||
|
||||
def test_find_zone_serial_error(self):
|
||||
self.backend._resolver = mock.Mock()
|
||||
self.backend._resolver.query.side_effect = RuntimeError('foo')
|
||||
|
||||
serial = self.backend.find_zone_serial('example.com')
|
||||
self.assertIsNone(serial)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_djbdns.execute')
|
||||
def test_create_zone(self, mock_execute):
|
||||
self.backend._perform_axfr_from_minidns = mock.Mock()
|
||||
self.backend._rebuild_data_cdb = mock.Mock()
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
def test_update_zone(self):
|
||||
self.backend._perform_axfr_from_minidns = mock.Mock()
|
||||
self.backend._rebuild_data_cdb = mock.Mock()
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
self.backend.update_zone(zone)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_djbdns.os.remove')
|
||||
def test_delete_zone(self, mock_rm):
|
||||
self.backend._rebuild_data_cdb = mock.Mock()
|
||||
|
||||
self.backend.delete_zone('foo')
|
||||
|
||||
mock_rm.assert_called_once_with(
|
||||
'/var/lib/djbdns/datafiles/foo.zonedata'
|
||||
)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_djbdns.os.remove')
|
||||
def test_exception_filter(self, mock_os_remove):
|
||||
self.backend._rebuild_data_cdb = mock.Mock()
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend.delete_zone, None
|
||||
)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_djbdns.os.remove')
|
||||
def test_exception_filter_pass_through(self, mock_os_remove):
|
||||
self.backend._rebuild_data_cdb = mock.Mock()
|
||||
|
||||
mock_os_remove.side_effect = exceptions.Backend
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend.delete_zone, 'foo'
|
||||
)
|
@ -1,47 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from designate.backend.agent_backend import impl_fake
|
||||
import designate.tests
|
||||
from designate.tests.unit.agent import backends
|
||||
|
||||
|
||||
class FakeAgentBackendTestCase(designate.tests.TestCase):
|
||||
def setUp(self):
|
||||
super(FakeAgentBackendTestCase, self).setUp()
|
||||
|
||||
self.CONF.set_override('listen', ['0.0.0.0:0'], 'service:agent')
|
||||
|
||||
self.backend = impl_fake.FakeBackend('foo')
|
||||
|
||||
def test_start_backend(self):
|
||||
self.backend.start()
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
def test_find_zone_serial(self):
|
||||
self.backend.find_zone_serial('example.org.')
|
||||
|
||||
def test_create_zone(self):
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
def test_update_zone(self):
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
self.backend.update_zone(zone)
|
||||
|
||||
def test_delete_zone(self):
|
||||
self.backend.delete_zone('example.org.')
|
@ -1,73 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
from designate.backend.agent_backend import impl_gdnsd
|
||||
import designate.tests
|
||||
|
||||
|
||||
class GdnsdAgentBackendTestCase(designate.tests.TestCase):
|
||||
@mock.patch.object(impl_gdnsd.GdnsdBackend, '_check_dirs')
|
||||
def setUp(self, mock_check_dirs):
|
||||
super(GdnsdAgentBackendTestCase, self).setUp()
|
||||
|
||||
self.backend = impl_gdnsd.GdnsdBackend('foo')
|
||||
|
||||
@mock.patch.object(impl_gdnsd.GdnsdBackend, '_check_conf')
|
||||
def test_start_backend(self, mock_check_conf):
|
||||
self.backend.start()
|
||||
self.assertTrue(mock_check_conf.called)
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(1, self.backend._resolver.timeout)
|
||||
self.assertEqual(1, self.backend._resolver.lifetime)
|
||||
self.assertEqual(['127.0.0.1'], self.backend._resolver.nameservers)
|
||||
self.assertEqual(
|
||||
'/etc/gdnsd/zones',
|
||||
self.backend._zonedir_path
|
||||
)
|
||||
self.assertEqual('gdnsd', self.backend._gdnsd_cmd_name)
|
||||
|
||||
def test_generate_zone_filename(self):
|
||||
zone_filename = self.backend._generate_zone_filename('A/bc-d_e.f')
|
||||
self.assertEqual('a@bc-d_e.f', zone_filename)
|
||||
|
||||
def test_find_zone_serial(self):
|
||||
class Data(object):
|
||||
serial = 3
|
||||
|
||||
self.backend._resolver = mock.Mock()
|
||||
self.backend._resolver.query.return_value = [Data(), ]
|
||||
serial = self.backend.find_zone_serial('example.com')
|
||||
self.assertEqual(3, serial)
|
||||
|
||||
def test_find_zone_serial_error(self):
|
||||
self.backend._resolver = mock.Mock()
|
||||
self.backend._resolver.query.side_effect = RuntimeError('foo')
|
||||
|
||||
serial = self.backend.find_zone_serial('example.com')
|
||||
self.assertIsNone(serial)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_gdnsd.os.remove')
|
||||
def test_delete_zone(self, mock_osremove):
|
||||
self.backend.delete_zone('foo-bar.example.org.')
|
||||
|
||||
mock_osremove.assert_called_once_with(
|
||||
'/etc/gdnsd/zones/foo-bar.example.org'
|
||||
)
|
@ -1,226 +0,0 @@
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
from designate.backend.agent_backend import impl_knot2
|
||||
from designate import exceptions
|
||||
import designate.tests
|
||||
from designate.tests.unit.agent import backends
|
||||
|
||||
|
||||
class Knot2AgentBackendTestCase(designate.tests.TestCase):
|
||||
def setUp(self):
|
||||
super(Knot2AgentBackendTestCase, self).setUp()
|
||||
|
||||
self.backend = impl_knot2.Knot2Backend('foo')
|
||||
self.backend._execute_knotc = mock.Mock()
|
||||
|
||||
def test_start_backend(self):
|
||||
self.backend.start()
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
def test_create_zone(self):
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
self.backend._execute_knotc.assert_has_calls([
|
||||
call('conf-begin'),
|
||||
call('conf-set', 'zone[example.org]',
|
||||
expected_error='duplicate identifier'),
|
||||
call('conf-commit'),
|
||||
call('zone-refresh', 'example.org')
|
||||
])
|
||||
|
||||
def test_create_zone_already_there(self):
|
||||
self.backend._execute_knotc.return_value = 'duplicate identifier'
|
||||
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
self.backend._execute_knotc.assert_has_calls([
|
||||
call('conf-begin'),
|
||||
call('conf-set', 'zone[example.org]',
|
||||
expected_error='duplicate identifier'),
|
||||
call('conf-commit'),
|
||||
call('zone-refresh', 'example.org')
|
||||
])
|
||||
|
||||
def test_start_minidns_to_knot_axfr(self):
|
||||
self.backend._start_minidns_to_knot_axfr('foo')
|
||||
|
||||
self.backend._execute_knotc.assert_called_with('zone-refresh', 'foo')
|
||||
|
||||
@mock.patch('oslo_concurrency.lockutils.lock')
|
||||
def test_modify_zone(self, mock_lock):
|
||||
self.backend._modify_zone('blah', 'bar')
|
||||
|
||||
self.assertEqual(3, self.backend._execute_knotc.call_count)
|
||||
|
||||
self.backend._execute_knotc.assert_called_with('conf-commit')
|
||||
|
||||
@mock.patch('oslo_concurrency.lockutils.lock')
|
||||
def test_modify_zone_exception(self, mock_lock):
|
||||
# Raise an exception during the second call to _execute_knotc
|
||||
self.backend._execute_knotc.side_effect = [None, exceptions.Backend,
|
||||
None]
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend._modify_zone, 'blah', 'bar'
|
||||
)
|
||||
|
||||
self.assertEqual(3, self.backend._execute_knotc.call_count)
|
||||
|
||||
self.backend._execute_knotc.assert_has_calls([
|
||||
call('conf-begin'),
|
||||
call('blah', 'bar'),
|
||||
call('conf-abort'),
|
||||
])
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_find_zone_serial(self, mock_execute):
|
||||
result = (
|
||||
'[example.com.] type: slave | serial: 20 | next-event: idle | '
|
||||
'auto-dnssec: disabled]'
|
||||
)
|
||||
mock_execute.return_value = result, ''
|
||||
|
||||
serial = self.backend.find_zone_serial('example.com')
|
||||
|
||||
self.assertEqual(20, serial)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_find_zone_serial_zone_not_found(self, mock_execute):
|
||||
mock_execute.side_effect = processutils.ProcessExecutionError(
|
||||
'error: [example.com.] (no such zone found)'
|
||||
)
|
||||
|
||||
serial = self.backend.find_zone_serial('example.com')
|
||||
|
||||
self.assertIsNone(serial)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_find_zone_serial_unexpected_output(self, mock_execute):
|
||||
mock_execute.return_value = 'bogus output', ''
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend.find_zone_serial, 'example.com'
|
||||
)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_find_zone_serial_error(self, mock_execute):
|
||||
mock_execute.side_effect = processutils.ProcessExecutionError('blah')
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend.find_zone_serial, 'example.com'
|
||||
)
|
||||
|
||||
def test_update_zone(self):
|
||||
zone = backends.create_dnspy_zone('example.org')
|
||||
|
||||
self.backend.update_zone(zone)
|
||||
|
||||
self.backend._execute_knotc.assert_called_once_with(
|
||||
'zone-refresh', 'example.org'
|
||||
)
|
||||
|
||||
def test_delete_zone(self):
|
||||
self.backend.delete_zone('example.org')
|
||||
|
||||
self.backend._execute_knotc.assert_has_calls([
|
||||
call('conf-begin'),
|
||||
call('conf-unset', 'zone[example.org]',
|
||||
expected_error='invalid identifier'),
|
||||
call('conf-commit'),
|
||||
])
|
||||
|
||||
def test_delete_zone_already_gone(self):
|
||||
self.backend._execute_knotc.return_value = 'duplicate identifier'
|
||||
|
||||
self.backend.delete_zone('example.org')
|
||||
|
||||
self.backend._execute_knotc.assert_has_calls([
|
||||
call('conf-begin'),
|
||||
call('conf-unset', 'zone[example.org]',
|
||||
expected_error='invalid identifier'),
|
||||
call('conf-commit'),
|
||||
])
|
||||
|
||||
|
||||
class Knot2AgentExecuteTestCase(designate.tests.TestCase):
|
||||
def setUp(self):
|
||||
super(Knot2AgentExecuteTestCase, self).setUp()
|
||||
|
||||
self.backend = impl_knot2.Knot2Backend('foo')
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual('knotc', self.backend._knotc_cmd_name)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_execute_knotc_ok(self, mock_execute):
|
||||
mock_execute.return_value = ('OK', '')
|
||||
|
||||
self.backend._execute_knotc('a1', 'a2')
|
||||
|
||||
mock_execute.assert_called_with('knotc', 'a1', 'a2')
|
||||
|
||||
self.assertEqual(1, mock_execute.call_count)
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_execute_knotc_expected_error(self, mock_execute):
|
||||
mock_execute.return_value = ('xyz', '')
|
||||
|
||||
self.backend._execute_knotc('a1', 'a2', expected_error='xyz')
|
||||
|
||||
mock_execute.assert_called_once_with('knotc', 'a1', 'a2')
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_execute_knotc_expected_output(self, mock_execute):
|
||||
mock_execute.return_value = ('xyz', '')
|
||||
|
||||
self.backend._execute_knotc('a1', 'a2', expected_output='xyz')
|
||||
|
||||
mock_execute.assert_called_once_with('knotc', 'a1', 'a2')
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_execute_knotc_with_error(self, mock_execute):
|
||||
mock_execute.return_value = ('xyz', '')
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend._execute_knotc, 'a1', 'a2'
|
||||
)
|
||||
|
||||
mock_execute.assert_called_once_with('knotc', 'a1', 'a2')
|
||||
|
||||
@mock.patch('designate.backend.agent_backend.impl_knot2.execute')
|
||||
def test_execute_knotc_raising_exception(self, mock_execute):
|
||||
mock_execute.side_effect = processutils.ProcessExecutionError
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.Backend,
|
||||
self.backend._execute_knotc, 'a1', 'a2'
|
||||
)
|
||||
|
||||
mock_execute.assert_called_once_with('knotc', 'a1', 'a2')
|
@ -1,141 +0,0 @@
|
||||
# Copyright 2016 Cloudbase Solutions Srl
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Author: Alin Balutoiu <abalutoiu@cloudbasesolutions.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
from os_win import constants
|
||||
from os_win import exceptions as os_win_exc
|
||||
from os_win import utilsfactory
|
||||
|
||||
from designate.backend.agent_backend import impl_msdns
|
||||
from designate import exceptions
|
||||
import designate.tests
|
||||
from designate.tests.unit.agent import backends
|
||||
|
||||
|
||||
class MSDNSAgentBackendTestCase(designate.tests.TestCase):
|
||||
@mock.patch.object(utilsfactory, 'get_dnsutils')
|
||||
def setUp(self, mock_get_dnsutils):
|
||||
super(MSDNSAgentBackendTestCase, self).setUp()
|
||||
self.zone_name = 'example.com'
|
||||
|
||||
self.CONF.set_override('masters', ['127.0.0.1:5354'], 'service:agent')
|
||||
|
||||
self.backend = impl_msdns.MSDNSBackend('foo')
|
||||
self.backend._dnsutils = mock.MagicMock()
|
||||
|
||||
def test_start_backend(self):
|
||||
self.backend.start()
|
||||
|
||||
def test_stop_backend(self):
|
||||
self.backend.stop()
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(['127.0.0.1'], self.backend._masters)
|
||||
|
||||
@mock.patch.object(utilsfactory, 'get_dnsutils')
|
||||
def test_init_no_masters(self, mock_get_dnsutils):
|
||||
self.CONF.set_override('masters', [], 'service:agent')
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exceptions.Backend,
|
||||
'Missing agent AXFR masters',
|
||||
impl_msdns.MSDNSBackend, 'foo'
|
||||
)
|
||||
|
||||
def test_find_zone_serial(self):
|
||||
serial = self.backend.find_zone_serial(self.zone_name)
|
||||
|
||||
expected_serial = self.backend._dnsutils.get_zone_serial.return_value
|
||||
self.assertEqual(expected_serial, serial)
|
||||
|
||||
self.backend._dnsutils.get_zone_serial.assert_called_once_with(
|
||||
self.zone_name
|
||||
)
|
||||
|
||||
def test_find_zone_serial_error(self):
|
||||
self.backend._dnsutils.get_zone_serial.side_effect = (
|
||||
os_win_exc.DNSZoneNotFound(zone_name=self.zone_name))
|
||||
|
||||
serial = self.backend.find_zone_serial(self.zone_name)
|
||||
|
||||
self.assertIsNone(serial)
|
||||
self.backend._dnsutils.get_zone_serial.assert_called_once_with(
|
||||
self.zone_name
|
||||
)
|
||||
|
||||
def test_create_zone(self):
|
||||
zone = backends.create_dnspy_zone(self.zone_name)
|
||||
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
self.backend._dnsutils.zone_create.assert_called_once_with(
|
||||
zone_name=self.zone_name,
|
||||
zone_type=constants.DNS_ZONE_TYPE_SECONDARY,
|
||||
ds_integrated=False,
|
||||
ip_addrs=self.backend._masters
|
||||
)
|
||||
|
||||
def test_create_zone_already_existing_diff(self):
|
||||
zone = backends.create_dnspy_zone(self.zone_name)
|
||||
self.backend._dnsutils.zone_create.side_effect = (
|
||||
os_win_exc.DNSZoneAlreadyExists(zone_name=self.zone_name))
|
||||
|
||||
self.assertRaises(
|
||||
os_win_exc.DNSZoneAlreadyExists,
|
||||
self.backend.create_zone, zone
|
||||
)
|
||||
|
||||
self.backend._dnsutils.get_zone_properties.assert_called_once_with(
|
||||
self.zone_name
|
||||
)
|
||||
|
||||
def test_create_zone_already_existing_identical(self):
|
||||
zone = backends.create_dnspy_zone(self.zone_name)
|
||||
self.backend._dnsutils.zone_create.side_effect = (
|
||||
os_win_exc.DNSZoneAlreadyExists(zone_name=self.zone_name)
|
||||
)
|
||||
|
||||
mock_zone_properties = {
|
||||
'zone_type': constants.DNS_ZONE_TYPE_SECONDARY,
|
||||
'ds_integrated': False,
|
||||
'master_servers': self.backend._masters
|
||||
}
|
||||
self.backend._dnsutils.get_zone_properties.return_value = (
|
||||
mock_zone_properties
|
||||
)
|
||||
|
||||
self.backend.create_zone(zone)
|
||||
|
||||
self.backend._dnsutils.get_zone_properties.assert_called_once_with(
|
||||
self.zone_name
|
||||
)
|
||||
|
||||
def test_update_zone(self):
|
||||
zone = backends.create_dnspy_zone(self.zone_name)
|
||||
|
||||
self.backend.update_zone(zone)
|
||||
|
||||
self.backend._dnsutils.zone_update.assert_called_once_with(
|
||||
self.zone_name
|
||||
)
|
||||
|
||||
def test_delete_zone(self):
|
||||
self.backend.delete_zone(self.zone_name)
|
||||
|
||||
self.backend._dnsutils.zone_delete.assert_called_once_with(
|
||||
self.zone_name
|
||||
)
|
@ -1,209 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# 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 binascii
|
||||
from unittest import mock
|
||||
|
||||
import dns
|
||||
import dns.resolver
|
||||
|
||||
import designate
|
||||
from designate.agent import handler
|
||||
from designate.backend import private_codes
|
||||
import designate.tests
|
||||
|
||||
|
||||
class AgentRequestHandlerTest(designate.tests.TestCase):
|
||||
def setUp(self):
|
||||
super(AgentRequestHandlerTest, self).setUp()
|
||||
|
||||
self.CONF.set_override('allow_notify', ['0.0.0.0'], 'service:agent')
|
||||
self.CONF.set_override('backend_driver', 'fake', 'service:agent')
|
||||
self.CONF.set_override('transfer_source', '1.2.3.4', 'service:agent')
|
||||
|
||||
self.handler = handler.RequestHandler()
|
||||
self.addr = ['0.0.0.0', 5558]
|
||||
|
||||
# TODO(johnsom) Remove this after the agents framework is removed or
|
||||
# the protocol has been updated to not use an unassigned opcode(14).
|
||||
dns.opcode.Opcode = private_codes.OpcodeWith14
|
||||
|
||||
def test_init(self):
|
||||
self.CONF.set_override('masters', ['192.0.2.1', '192.0.2.2'],
|
||||
'service:agent')
|
||||
|
||||
hndlr = handler.RequestHandler()
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
{'host': '192.0.2.1', 'port': 53},
|
||||
{'host': '192.0.2.2', 'port': 53}
|
||||
],
|
||||
hndlr.masters
|
||||
)
|
||||
|
||||
@mock.patch.object(dns.resolver.Resolver, 'query')
|
||||
@mock.patch('designate.dnsutils.do_axfr')
|
||||
def test_receive_notify(self, mock_doaxfr, mock_query):
|
||||
"""
|
||||
Get a NOTIFY and ensure the response is right,
|
||||
and an AXFR is triggered
|
||||
"""
|
||||
payload = ('1a7220000001000000000000076578616d706c6503636f6d000006'
|
||||
'0001')
|
||||
# expected response is NOERROR, other fields are
|
||||
# opcode NOTIFY
|
||||
# rcode NOERROR
|
||||
# flags QR AA
|
||||
# ;QUESTION
|
||||
# example.com. IN SOA
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'1a72a4000001000000000000076578616d706c6503'
|
||||
b'636f6d0000060001')
|
||||
request = dns.message.from_wire(binascii.a2b_hex(payload))
|
||||
request.environ = {'addr': ['0.0.0.0', 1234]}
|
||||
response = next(self.handler(request)).to_wire()
|
||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
||||
|
||||
def test_receive_notify_bad_notifier(self):
|
||||
payload = '243520000001000000000000076578616d706c6503636f6d0000060001'
|
||||
# expected response is REFUSED, other fields are
|
||||
# opcode NOTIFY
|
||||
# rcode REFUSED
|
||||
# flags QR
|
||||
# ;QUESTION
|
||||
# example.com. IN SOA
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'2435a0050001000000000000076578616d706c6503636f'
|
||||
b'6d0000060001')
|
||||
request = dns.message.from_wire(binascii.a2b_hex(payload))
|
||||
# Bad 'requester'
|
||||
request.environ = {'addr': ['6.6.6.6', 1234]}
|
||||
response = next(self.handler(request)).to_wire()
|
||||
|
||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
||||
|
||||
@mock.patch.object(dns.resolver.Resolver, 'query')
|
||||
@mock.patch('designate.dnsutils.do_axfr')
|
||||
def test_receive_create(self, mock_doaxfr, mock_query):
|
||||
payload = '735d70000001000000000000076578616d706c6503636f6d00ff02ff00'
|
||||
# Expected NOERROR other fields are
|
||||
# opcode 14
|
||||
# rcode NOERROR
|
||||
# flags QR AA
|
||||
# ;QUESTION
|
||||
# example.com. CLASS65280 TYPE65282
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'735df4000001000000000000076578616d706c6503636f'
|
||||
b'6d00ff02ff00')
|
||||
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_zone_serial', return_value=None):
|
||||
response = next(self.handler(request)).to_wire()
|
||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
||||
|
||||
def test_receive_create_bad_notifier(self):
|
||||
payload = '8dfd70000001000000000000076578616d706c6503636f6d00ff02ff00'
|
||||
# expected response is REFUSED, other fields are
|
||||
# opcode 14
|
||||
# rcode REFUSED
|
||||
# flags QR
|
||||
# ;QUESTION
|
||||
# example.com. CLASS65280 TYPE65282
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'8dfdf0050001000000000000076578616d706c6503636f'
|
||||
b'6d00ff02ff00')
|
||||
request = dns.message.from_wire(binascii.a2b_hex(payload))
|
||||
# Bad 'requester'
|
||||
request.environ = {'addr': ['6.6.6.6', 1234]}
|
||||
response = next(self.handler(request)).to_wire()
|
||||
|
||||
self.assertEqual(binascii.b2a_hex(response), expected_response)
|
||||
|
||||
@mock.patch('designate.utils.execute')
|
||||
def test_receive_delete(self, mock_execute):
|
||||
payload = '3b9970000001000000000000076578616d706c6503636f6d00ff03ff00'
|
||||
# Expected NOERROR other fields are
|
||||
# opcode 14
|
||||
# rcode NOERROR
|
||||
# flags QR AA
|
||||
# ;QUESTION
|
||||
# example.com. CLASS65280 TYPE65283
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'3b99f4000001000000000000076578616d706c6503636f'
|
||||
b'6d00ff03ff00')
|
||||
request = dns.message.from_wire(binascii.a2b_hex(payload))
|
||||
request.environ = {'addr': ['0.0.0.0', 1234]}
|
||||
response = next(self.handler(request)).to_wire()
|
||||
|
||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
||||
|
||||
def test_receive_delete_bad_notifier(self):
|
||||
payload = 'e6da70000001000000000000076578616d706c6503636f6d00ff03ff00'
|
||||
# expected response is REFUSED, other fields are
|
||||
# opcode 14
|
||||
# rcode REFUSED
|
||||
# flags QR
|
||||
# ;QUESTION
|
||||
# example.com. CLASS65280 TYPE65283
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'e6daf0050001000000000000076578616d706c6503636f'
|
||||
b'6d00ff03ff00')
|
||||
request = dns.message.from_wire(binascii.a2b_hex(payload))
|
||||
# Bad 'requester'
|
||||
request.environ = {'addr': ['6.6.6.6', 1234]}
|
||||
response = next(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, mock_doaxfr, mock_query):
|
||||
payload = '735d70000001000000000000076578616d706c6503636f6d00ff02ff00'
|
||||
# Expected NOERROR other fields are
|
||||
# opcode 14
|
||||
# rcode NOERROR
|
||||
# flags QR AA
|
||||
# ;QUESTION
|
||||
# example.com. CLASS65280 TYPE65282
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
expected_response = (b'735df4000001000000000000076578616d706c6503636f'
|
||||
b'6d00ff02ff00')
|
||||
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_zone_serial', return_value=None):
|
||||
response = next(self.handler(request)).to_wire()
|
||||
mock_doaxfr.assert_called_with(
|
||||
'example.com.', [], source='1.2.3.4'
|
||||
)
|
||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
@ -1,78 +0,0 @@
|
||||
# Copyright 2014 Rackspace Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from unittest import mock
|
||||
|
||||
from designate.agent import service
|
||||
from designate.backend import agent_backend
|
||||
from designate.backend.agent_backend import impl_fake
|
||||
from designate import dnsmiddleware
|
||||
import designate.tests
|
||||
from designate.tests import fixtures
|
||||
from designate import utils
|
||||
|
||||
|
||||
class AgentServiceTest(designate.tests.TestCase):
|
||||
def setUp(self):
|
||||
super(AgentServiceTest, self).setUp()
|
||||
self.stdlog = fixtures.StandardLogging()
|
||||
self.useFixture(self.stdlog)
|
||||
|
||||
self.CONF.set_override('listen', ['0.0.0.0:0'], 'service:agent')
|
||||
self.CONF.set_override('notify_delay', 0, 'service:agent')
|
||||
|
||||
self.service = service.Service()
|
||||
self.service.dns_service._start = mock.Mock()
|
||||
|
||||
def test_service_start(self):
|
||||
self.service.start()
|
||||
|
||||
self.assertTrue(self.service.dns_service._start.called)
|
||||
|
||||
def test_service_stop(self):
|
||||
self.service.dns_service.stop = mock.Mock()
|
||||
self.service.backend.stop = mock.Mock()
|
||||
|
||||
self.service.stop()
|
||||
|
||||
self.assertTrue(self.service.dns_service.stop.called)
|
||||
self.assertTrue(self.service.backend.stop.called)
|
||||
|
||||
self.assertIn('Stopping agent service', self.stdlog.logger.output)
|
||||
|
||||
def test_service_name(self):
|
||||
self.assertEqual('agent', self.service.service_name)
|
||||
|
||||
def test_get_backend(self):
|
||||
backend = agent_backend.get_backend('fake', agent_service=self.service)
|
||||
self.assertIsInstance(backend, impl_fake.FakeBackend)
|
||||
|
||||
@mock.patch.object(utils, 'cache_result')
|
||||
def test_get_dns_application(self, mock_cache_result):
|
||||
self.assertIsInstance(
|
||||
self.service.dns_application,
|
||||
dnsmiddleware.SerializationMiddleware
|
||||
)
|
||||
|
||||
@mock.patch.object(utils, 'cache_result')
|
||||
def test_get_dns_application_with_notify_delay(self, mock_cache_result):
|
||||
self.service = service.Service()
|
||||
|
||||
self.CONF.set_override('notify_delay', 1.0, 'service:agent')
|
||||
|
||||
self.assertIsInstance(
|
||||
self.service.dns_application,
|
||||
dnsmiddleware.SerializationMiddleware
|
||||
)
|
@ -1,185 +0,0 @@
|
||||
# Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import dns
|
||||
import dns.query
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as cfg_fixture
|
||||
import oslotest.base
|
||||
|
||||
import designate.backend.agent as agent
|
||||
import designate.backend.private_codes as pcodes
|
||||
from designate import context
|
||||
from designate import dnsutils
|
||||
from designate import exceptions
|
||||
from designate import objects
|
||||
from designate.tests.unit import RoObject
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class AgentBackendTestCase(oslotest.base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(AgentBackendTestCase, self).setUp()
|
||||
self.useFixture(cfg_fixture.Config(CONF))
|
||||
|
||||
self.context = mock.Mock()
|
||||
self.admin_context = mock.Mock()
|
||||
mock.patch.object(
|
||||
context.DesignateContext, 'get_admin_context',
|
||||
return_value=self.admin_context).start()
|
||||
|
||||
CONF.set_override('poll_timeout', 1, 'service:worker')
|
||||
CONF.set_override('poll_retry_interval', 4, 'service:worker')
|
||||
CONF.set_override('poll_max_retries', 5, 'service:worker')
|
||||
CONF.set_override('poll_delay', 6, 'service:worker')
|
||||
|
||||
self.zone = objects.Zone(
|
||||
id='e2bed4dc-9d01-11e4-89d3-123b93f75cba',
|
||||
name='example.com.',
|
||||
email='example@example.com',
|
||||
)
|
||||
self.target = {
|
||||
'id': '4588652b-50e7-46b9-b688-a9bad40a873e',
|
||||
'type': 'agent',
|
||||
'masters': [],
|
||||
'options': [
|
||||
{'key': 'host', 'value': 2},
|
||||
{'key': 'port', 'value': 3},
|
||||
],
|
||||
}
|
||||
|
||||
self.backend = agent.AgentPoolBackend(
|
||||
objects.PoolTarget.from_dict(self.target)
|
||||
)
|
||||
|
||||
def test_create_zone(self):
|
||||
self.backend._make_and_send_dns_message = mock.Mock(
|
||||
return_value=1
|
||||
)
|
||||
|
||||
out = self.backend.create_zone(self.context, self.zone)
|
||||
|
||||
self.backend._make_and_send_dns_message.assert_called_with(
|
||||
self.zone.name, 1, 14, pcodes.CREATE, pcodes.SUCCESS, 2, 3)
|
||||
self.assertIsNone(out)
|
||||
|
||||
def test_create_zone_exception(self):
|
||||
self.backend._make_and_send_dns_message = mock.Mock(
|
||||
return_value=None
|
||||
)
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exceptions.Backend, 'Failed create_zone()',
|
||||
self.backend.create_zone, self.context, self.zone,
|
||||
)
|
||||
|
||||
self.backend._make_and_send_dns_message.assert_called_with(
|
||||
self.zone.name, 1, 14, pcodes.CREATE, pcodes.SUCCESS, 2, 3)
|
||||
|
||||
def test_update_zone(self):
|
||||
self.assertIsNone(self.backend.update_zone(self.context, self.zone))
|
||||
|
||||
def test_delete_zone(self):
|
||||
self.backend._make_and_send_dns_message = mock.Mock(
|
||||
return_value=(1, 2))
|
||||
|
||||
out = self.backend.delete_zone(self.context, self.zone)
|
||||
|
||||
self.backend._make_and_send_dns_message.assert_called_with(
|
||||
self.zone.name, 1, 14, pcodes.DELETE, pcodes.SUCCESS, 2, 3)
|
||||
self.assertIsNone(out)
|
||||
|
||||
def test_delete_zone_exception(self):
|
||||
self.backend._make_and_send_dns_message = mock.Mock(
|
||||
return_value=None
|
||||
)
|
||||
|
||||
self.assertRaisesRegex(
|
||||
exceptions.Backend, 'Failed delete_zone()',
|
||||
self.backend.delete_zone, self.context, self.zone,
|
||||
)
|
||||
|
||||
self.backend._make_and_send_dns_message.assert_called_with(
|
||||
self.zone.name, 1, 14, pcodes.DELETE, pcodes.SUCCESS, 2, 3)
|
||||
|
||||
@mock.patch.object(dnsutils, 'send_dns_message')
|
||||
def test_make_and_send_dns_message_timeout(self, mock_send_dns_message):
|
||||
self.backend._make_dns_message = mock.Mock(return_value='')
|
||||
mock_send_dns_message.side_effect = dns.exception.Timeout()
|
||||
|
||||
self.assertIsNone(
|
||||
self.backend._make_and_send_dns_message('h', 123, 1, 2, 3, 4, 5)
|
||||
)
|
||||
|
||||
@mock.patch.object(dnsutils, 'send_dns_message')
|
||||
def test_make_and_send_dns_message_bad_response(self,
|
||||
mock_send_dns_message):
|
||||
self.backend._make_dns_message = mock.Mock(return_value='')
|
||||
mock_send_dns_message.side_effect = dns.query.BadResponse()
|
||||
|
||||
self.assertIsNone(
|
||||
self.backend._make_and_send_dns_message('h', 123, 1, 2, 3, 4, 5)
|
||||
)
|
||||
|
||||
@mock.patch.object(dnsutils, 'send_dns_message')
|
||||
def test_make_and_send_dns_message_missing_AA_flags(self,
|
||||
mock_send_dns_message):
|
||||
self.backend._make_dns_message = mock.Mock(return_value='')
|
||||
response = RoObject(
|
||||
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
|
||||
# rcode is NOERROR but (flags & dns.flags.AA) gives 0
|
||||
flags=0,
|
||||
)
|
||||
mock_send_dns_message.return_value = response
|
||||
|
||||
self.assertIsNone(
|
||||
self.backend._make_and_send_dns_message('h', 123, 1, 2, 3, 4, 5)
|
||||
)
|
||||
|
||||
@mock.patch.object(dnsutils, 'send_dns_message')
|
||||
def test_make_and_send_dns_message_error_flags(self,
|
||||
mock_send_dns_message):
|
||||
self.backend._make_dns_message = mock.Mock(return_value='')
|
||||
response = RoObject(
|
||||
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
|
||||
# rcode is NOERROR but flags are not NOERROR
|
||||
flags=123,
|
||||
ednsflags=321
|
||||
)
|
||||
mock_send_dns_message.return_value = response
|
||||
|
||||
self.assertIsNone(
|
||||
self.backend._make_and_send_dns_message('h', 123, 1, 2, 3, 4, 5)
|
||||
)
|
||||
|
||||
@mock.patch.object(dnsutils, 'send_dns_message')
|
||||
def test_make_and_send_dns_message(self, mock_send_dns_message):
|
||||
self.backend._make_dns_message = mock.Mock(return_value='')
|
||||
response = RoObject(
|
||||
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
|
||||
flags=agent.dns.flags.AA,
|
||||
ednsflags=321
|
||||
)
|
||||
mock_send_dns_message.return_value = response
|
||||
|
||||
self.assertEqual(
|
||||
response,
|
||||
self.backend._make_and_send_dns_message('h', 123, 1, 2, 3, 4, 5)
|
||||
)
|
@ -16,7 +16,6 @@ from oslo_config import cfg
|
||||
from oslo_config import fixture as cfg_fixture
|
||||
import oslotest.base
|
||||
|
||||
from designate.cmd import agent
|
||||
from designate.cmd import api
|
||||
from designate.cmd import central
|
||||
from designate.cmd import mdns
|
||||
@ -37,20 +36,6 @@ class CmdTestCase(oslotest.base.BaseTestCase):
|
||||
super(CmdTestCase, self).setUp()
|
||||
self.useFixture(cfg_fixture.Config(CONF))
|
||||
|
||||
@mock.patch('designate.agent.service.Service')
|
||||
def test_agent(self, mock_service, mock_read_config, mock_log_setup,
|
||||
mock_heartbeat, mock_serve, mock_wait):
|
||||
CONF.set_override('workers', 1, 'service:agent')
|
||||
|
||||
agent.main()
|
||||
|
||||
mock_read_config.assert_called_with('designate', mock.ANY)
|
||||
mock_log_setup.assert_called_with(mock.ANY, 'designate')
|
||||
mock_service.assert_called_with()
|
||||
mock_heartbeat.assert_called()
|
||||
mock_serve.assert_called_with(mock.ANY, workers=1)
|
||||
mock_wait.assert_called_with()
|
||||
|
||||
@mock.patch('designate.api.service.Service')
|
||||
def test_api(self, mock_service, mock_read_config, mock_log_setup,
|
||||
mock_heartbeat, mock_serve, mock_wait):
|
||||
|
@ -1,103 +0,0 @@
|
||||
# Configure the agent backend
|
||||
|
||||
# Enable with:
|
||||
# DESIGNATE_BACKEND_DRIVER=agent
|
||||
# DESIGNATE_AGENT_BACKEND_DRIVER=<an agent backend>
|
||||
|
||||
# Dependencies:
|
||||
# ``functions`` file
|
||||
# ``designate`` configuration
|
||||
|
||||
# install_designate_backend - install any external requirements
|
||||
# configure_designate_backend - make configuration changes, including those to other services
|
||||
# init_designate_backend - initialize databases, etc.
|
||||
# start_designate_backend - start any external services
|
||||
# stop_designate_backend - stop any external services
|
||||
# cleanup_designate_backend - remove transient data and cache
|
||||
|
||||
# Save trace setting
|
||||
DP_AGENT_XTRACE=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
# Get agent backend configuration
|
||||
# -------------------------------
|
||||
if [[ -r $DESIGNATE_PLUGINS/backend-agent-$DESIGNATE_AGENT_BACKEND_DRIVER ]]; then
|
||||
# Load plugin
|
||||
source $DESIGNATE_PLUGINS/backend-agent-$DESIGNATE_AGENT_BACKEND_DRIVER
|
||||
fi
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# install_designate_backend - install any external requirements
|
||||
function install_designate_backend {
|
||||
# Install the Agent Backend
|
||||
install_designate_agent_backend
|
||||
}
|
||||
|
||||
# configure_designate_backend - make configuration changes, including those to other services
|
||||
function configure_designate_backend {
|
||||
# Generate Designate pool.yaml file
|
||||
sudo tee $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
|
||||
---
|
||||
- name: default
|
||||
description: DevStack Agent Pool
|
||||
attributes: {}
|
||||
|
||||
ns_records:
|
||||
- hostname: $DESIGNATE_DEFAULT_NS_RECORD
|
||||
priority: 1
|
||||
|
||||
nameservers:
|
||||
- host: $(ipv6_unquote $DESIGNATE_SERVICE_HOST)
|
||||
port: $DESIGNATE_SERVICE_PORT_DNS
|
||||
|
||||
targets:
|
||||
- type: agent
|
||||
description: Agent Instance
|
||||
|
||||
masters:
|
||||
- host: $(ipv6_unquote $DESIGNATE_SERVICE_HOST)
|
||||
port: $DESIGNATE_SERVICE_PORT_MDNS
|
||||
|
||||
options:
|
||||
host: $(ipv6_unquote $DESIGNATE_SERVICE_HOST)
|
||||
port: $DESIGNATE_SERVICE_PORT_AGENT
|
||||
EOF
|
||||
|
||||
# Configure Agent Settings
|
||||
iniset $DESIGNATE_CONF service:agent backend_driver $DESIGNATE_AGENT_BACKEND_DRIVER
|
||||
iniset $DESIGNATE_CONF service:agent host $(ipv6_unquote $DESIGNATE_SERVICE_HOST)
|
||||
iniset $DESIGNATE_CONF service:agent port $DESIGNATE_SERVICE_PORT_AGENT
|
||||
iniset $DESIGNATE_CONF service:agent masters "$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"
|
||||
|
||||
# Configure the Agent Backend
|
||||
configure_designate_agent_backend
|
||||
}
|
||||
|
||||
# init_designate_backend - initialize databases, etc.
|
||||
function init_designate_backend {
|
||||
# Init the Agent Backend
|
||||
init_designate_agent_backend
|
||||
}
|
||||
|
||||
# start_designate_backend - start any external services
|
||||
function start_designate_backend {
|
||||
# Start the Agent Backend
|
||||
start_designate_agent_backend
|
||||
}
|
||||
|
||||
# stop_designate_backend - stop any external services
|
||||
function stop_designate_backend {
|
||||
# Stop the Agent Backend
|
||||
stop_designate_agent_backend
|
||||
}
|
||||
|
||||
# cleanup_designate_backend - remove transient data and cache
|
||||
function cleanup_designate_backend {
|
||||
# Cleanup the Agent Backend
|
||||
cleanup_designate_agent_backend
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$DP_AGENT_XTRACE
|
@ -1,55 +0,0 @@
|
||||
# Configure the fake agent backend
|
||||
|
||||
# Enable with:
|
||||
# DESIGNATE_BACKEND_DRIVER=agent
|
||||
# DESIGNATE_AGENT_BACKEND_DRIVER=fake
|
||||
|
||||
# install_designate_agent_backend - install any external requirements
|
||||
# configure_designate_agent_backend - make configuration changes, including those to other services
|
||||
# init_designate_agent_backend - initialize databases, etc.
|
||||
# start_designate_agent_backend - start any external services
|
||||
# stop_designate_agent_backend - stop any external services
|
||||
# cleanup_designate_agent_backend - remove transient data and cache
|
||||
|
||||
# Save trace setting
|
||||
DP_AGENT_FAKE_XTRACE=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# install_designate_agent_backend - install any external requirements
|
||||
function install_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# configure_designate_agent_backend - make configuration changes, including those to other services
|
||||
function configure_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# init_designate_agent_backend - initialize databases, etc.
|
||||
function init_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# start_designate_agent_backend - start any external services
|
||||
function start_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# stop_designate_agent_backend - stop any external services
|
||||
function stop_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# cleanup_designate_agent_backend - remove transient data and cache
|
||||
function cleanup_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$DP_AGENT_FAKE_XTRACE
|
@ -1,130 +0,0 @@
|
||||
# Configure the Knot2 agent backend for Devstack
|
||||
|
||||
# Enable this pluging by adding these line to local.conf:
|
||||
#
|
||||
# DESIGNATE_BACKEND_DRIVER=agent
|
||||
# DESIGNATE_AGENT_BACKEND_DRIVER=knot2
|
||||
|
||||
# install_designate_agent_backend - install any external requirements
|
||||
# configure_designate_agent_backend - make configuration changes, including those to other services
|
||||
# init_designate_agent_backend - initialize databases, etc.
|
||||
# start_designate_agent_backend - start any external services
|
||||
# stop_designate_agent_backend - stop any external services
|
||||
# cleanup_designate_agent_backend - remove transient data and cache
|
||||
|
||||
# Save trace setting
|
||||
DP_AGENT_KNOT_XTRACE=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
KNOT_SERVICE_NAME=knot
|
||||
KNOT_CFG_DIR=/etc/knot
|
||||
KNOT_VAR_DIR=/var/lib/knot
|
||||
KNOT_USER=knot
|
||||
KNOT_GROUP=knot
|
||||
|
||||
if is_fedora; then
|
||||
echo "only Ubuntu is supported right now"
|
||||
fi
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# install_designate_agent_backend - install any external requirements
|
||||
function install_designate_agent_backend {
|
||||
if is_ubuntu; then
|
||||
# https://github.com/oerdnj/deb.sury.org/issues/56
|
||||
LC_ALL=C.UTF-8 sudo add-apt-repository --yes ppa:cz.nic-labs/knot-dns
|
||||
sudo apt-get update
|
||||
echo "---- available knot package ---"
|
||||
sudo apt-cache show knot
|
||||
echo "---- installing knot ---"
|
||||
sudo apt-get install -y knot
|
||||
else
|
||||
echo "only Ubuntu is supported right now"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# configure_designate_agent_backend - make configuration changes, including those to other services
|
||||
function configure_designate_agent_backend {
|
||||
|
||||
# [re]create the config database
|
||||
stop_service knot
|
||||
sudo sh -c "rm /var/lib/knot/*zone /var/lib/knot/*/*.mdb -f"
|
||||
sudo knotc conf-init -v
|
||||
|
||||
# Create /etc/default/knot
|
||||
cat <<EOF | sudo tee /etc/default/knot
|
||||
# Created by $0 on $(date)
|
||||
KNOTD_ARGS="-C /var/lib/knot/confdb"
|
||||
EOF
|
||||
|
||||
# Apply this workaround for bug
|
||||
# https://gitlab.labs.nic.cz/labs/knot/issues/455
|
||||
sudo sh -c "cd /etc/default/ && test -f knotd || ln -s knot knotd"
|
||||
|
||||
start_service knot
|
||||
sleep 1
|
||||
|
||||
# Ensure the confdb is present
|
||||
sudo test -f /var/lib/knot/confdb/data.mdb
|
||||
|
||||
# Create the configuration
|
||||
MINIDNS_IPADDR=$(ipv6_unquote $DESIGNATE_SERVICE_HOST)
|
||||
|
||||
sudo knotc conf-begin
|
||||
sudo knotc conf-set server.listen $(ipv6_unquote $DESIGNATE_SERVICE_HOST)@$DESIGNATE_SERVICE_PORT_DNS
|
||||
sudo knotc conf-set remote[minidns]
|
||||
sudo knotc conf-set remote[minidns].address $(ipv6_unquote $DESIGNATE_SERVICE_HOST)@$DESIGNATE_SERVICE_PORT_MDNS
|
||||
sudo knotc conf-set template[default]
|
||||
sudo knotc conf-set template[default].master minidns
|
||||
sudo knotc conf-set template[default].acl acl_minidns
|
||||
sudo knotc conf-set template[default].semantic-checks on
|
||||
# Create localdomain as a workaround for
|
||||
# https://gitlab.labs.nic.cz/labs/knot/issues/457
|
||||
sudo knotc conf-set zone[localdomain]
|
||||
sudo knotc conf-set log.any info
|
||||
sudo knotc conf-set log.target syslog
|
||||
sudo knotc conf-set acl[acl_minidns]
|
||||
sudo knotc conf-set acl[acl_minidns].address $DESIGNATE_SERVICE_HOST
|
||||
sudo knotc conf-set acl[acl_minidns].action notify
|
||||
echo "--------------"
|
||||
sudo knotc conf-diff
|
||||
echo "--------------"
|
||||
sudo knotc conf-commit
|
||||
sudo knotc conf-check
|
||||
|
||||
# Ensure the zone survives a restart
|
||||
sleep 1
|
||||
sudo service knot restart
|
||||
sleep 1
|
||||
sudo knotc zone-status localdomain
|
||||
|
||||
echo "Testing Knot: this should return the daemon version"
|
||||
dig @$(ipv6_unquote $DESIGNATE_SERVICE_HOST) -p$DESIGNATE_SERVICE_PORT_DNS version.server CH TXT
|
||||
}
|
||||
|
||||
# init_designate_agent_backend - initialize databases, etc.
|
||||
function init_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# start_designate_agent_backend - start any external services
|
||||
function start_designate_agent_backend {
|
||||
start_service knot
|
||||
}
|
||||
|
||||
# stop_designate_agent_backend - stop any external services
|
||||
function stop_designate_agent_backend {
|
||||
stop_service knot
|
||||
}
|
||||
|
||||
# cleanup_designate_agent_backend - remove transient data and cache
|
||||
function cleanup_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$DP_AGENT_KNOT_XTRACE
|
@ -1,116 +0,0 @@
|
||||
# Configure the agent backend
|
||||
|
||||
# Enable this pluging by adding these line to local.conf:
|
||||
#
|
||||
# DESIGNATE_BACKEND_DRIVER=agent
|
||||
# DESIGNATE_AGENT_BACKEND_DRIVER=msdns
|
||||
|
||||
# Dependencies:
|
||||
# ``functions`` file
|
||||
# ``designate`` configuration
|
||||
|
||||
# install_designate_agent_backend - install any external requirements
|
||||
# configure_designate_agent_backend - make configuration changes, including those to other services
|
||||
# init_designate_agent_backend - initialize databases, etc.
|
||||
# start_designate_agent_backend - start any external services
|
||||
# stop_designate_agent_backend - stop any external services
|
||||
# cleanup_designate_agent_backend - remove transient data and cache
|
||||
|
||||
# Save trace setting
|
||||
DP_AGENT_MSDNS_XTRACE=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
|
||||
DESIGNATE_MSDNS_MASTERS=${DESIGNATE_MSDNS_MASTERS:-"$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"}
|
||||
DESIGNATE_MSDNS_HOST_IP=${DESIGNATE_MSDNS_HOST_IP:-}
|
||||
DESIGNATE_MSDNS_HOST_PORT=${DESIGNATE_MSDNS_HOST_PORT:-}
|
||||
|
||||
# Sanity Checks
|
||||
# -------------
|
||||
if [ -z "$DESIGNATE_MSDNS_MASTERS" ]; then
|
||||
die $LINENO "You must configure DESIGNATE_MSDNS_MASTERS"
|
||||
fi
|
||||
|
||||
if [ -z "$DESIGNATE_MSDNS_HOST_IP" ]; then
|
||||
die $LINENO "You must configure DESIGNATE_MSDNS_HOST_IP with the IP of the MS DNS host"
|
||||
fi
|
||||
|
||||
if [ -z "$DESIGNATE_MSDNS_HOST_PORT" ]; then
|
||||
die $LINENO "You must configure DESIGNATE_MSDNS_HOST_PORT with the PORT of the MS DNS host"
|
||||
fi
|
||||
|
||||
if [ "$DESIGNATE_SERVICE_PORT_MDNS" != "53" ]; then
|
||||
die $LINENO "Microsoft DNS requires DESIGNATE_SERVICE_PORT_MDNS to be set to '53'"
|
||||
fi
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# install_designate_agent_backend - install any external requirements
|
||||
function install_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# configure_designate_agent_backend - make configuration changes, including those to other services
|
||||
function configure_designate_agent_backend {
|
||||
# Generate Designate pool.yaml file
|
||||
sudo tee $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
|
||||
---
|
||||
- name: default
|
||||
description: DevStack MSDNS Pool
|
||||
attributes: {}
|
||||
|
||||
ns_records:
|
||||
- hostname: $DESIGNATE_DEFAULT_NS_RECORD
|
||||
priority: 1
|
||||
|
||||
nameservers:
|
||||
- host: $DESIGNATE_MSDNS_HOST_IP
|
||||
port: $DESIGNATE_MSDNS_HOST_PORT
|
||||
|
||||
targets:
|
||||
- type: agent
|
||||
description: MSDNS Agent Instance
|
||||
|
||||
masters:
|
||||
- host: $(ipv6_unquote $DESIGNATE_SERVICE_HOST)
|
||||
port: $DESIGNATE_SERVICE_PORT_MDNS
|
||||
|
||||
options:
|
||||
host: $DESIGNATE_MSDNS_HOST_IP
|
||||
port: $DESIGNATE_MSDNS_HOST_PORT
|
||||
EOF
|
||||
|
||||
echo "# Sample Config for Windows Agent service" | sudo tee ${DESIGNATE_CONF}.win
|
||||
echo "# This file should be copied to the Windows DNS server and used as the Designate config file" | sudo tee -a ${DESIGNATE_CONF}.win
|
||||
# Configure Agent Settings
|
||||
iniset ${DESIGNATE_CONF}.win service:agent backend_driver $DESIGNATE_AGENT_BACKEND_DRIVER
|
||||
iniset ${DESIGNATE_CONF}.win service:agent host $DESIGNATE_MSDNS_HOST_IP
|
||||
iniset ${DESIGNATE_CONF}.win service:agent port $DESIGNATE_MSDNS_HOST_PORT
|
||||
iniset ${DESIGNATE_CONF}.win service:agent masters "$DESIGNATE_MSDNS_MASTERS"
|
||||
}
|
||||
|
||||
# init_designate_agent_backend - initialize databases, etc.
|
||||
function init_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# start_designate_agent_backend - start any external services
|
||||
function start_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# stop_designate_agent_backend - stop any external services
|
||||
function stop_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# cleanup_designate_agent_backend - remove transient data and cache
|
||||
function cleanup_designate_agent_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$DP_AGENT_MSDNS_XTRACE
|
@ -58,9 +58,6 @@ function configure_designate {
|
||||
iniset $DESIGNATE_CONF coordination backend_url $DESIGNATE_COORDINATION_URL
|
||||
fi
|
||||
|
||||
# Agent Configuration
|
||||
iniset $DESIGNATE_CONF service:agent workers $API_WORKERS
|
||||
|
||||
# API Configuration
|
||||
sudo cp $DESIGNATE_DIR/etc/designate/api-paste.ini $DESIGNATE_APIPASTE_CONF
|
||||
iniset $DESIGNATE_CONF service:api enabled_extensions_v2 $DESIGNATE_ENABLED_EXTENSIONS_V2
|
||||
@ -278,7 +275,6 @@ function start_designate {
|
||||
|
||||
run_process designate-central "$DESIGNATE_BIN_DIR/designate-central --config-file $DESIGNATE_CONF"
|
||||
run_process designate-mdns "$DESIGNATE_BIN_DIR/designate-mdns --config-file $DESIGNATE_CONF"
|
||||
run_process designate-agent "$DESIGNATE_BIN_DIR/designate-agent --config-file $DESIGNATE_CONF"
|
||||
run_process designate-sink "$DESIGNATE_BIN_DIR/designate-sink --config-file $DESIGNATE_CONF"
|
||||
|
||||
run_process designate-worker "$DESIGNATE_BIN_DIR/designate-worker --config-file $DESIGNATE_CONF"
|
||||
@ -314,7 +310,6 @@ function stop_designate {
|
||||
|
||||
stop_process designate-central
|
||||
stop_process designate-mdns
|
||||
stop_process designate-agent
|
||||
stop_process designate-sink
|
||||
stop_process designate-worker
|
||||
stop_process designate-producer
|
||||
@ -324,12 +319,7 @@ function stop_designate {
|
||||
|
||||
# This is the main for plugin.sh
|
||||
if is_service_enabled designate; then
|
||||
# Sanify check for agent backend
|
||||
# ------------------------------
|
||||
if ! is_service_enabled designate-agent && [ "$DESIGNATE_BACKEND_DRIVER" == "agent" ]; then
|
||||
die $LINENO "To use the agent backend, you must enable the designate-agent service"
|
||||
fi
|
||||
|
||||
if [[ "$1" == "stack" && "$2" == "install" ]]; then
|
||||
echo_summary "Installing Designate client"
|
||||
install_designateclient
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Default options
|
||||
DESIGNATE_BACKEND_DRIVER=${DESIGNATE_BACKEND_DRIVER:=bind9}
|
||||
DESIGNATE_AGENT_BACKEND_DRIVER=${DESIGNATE_AGENT_BACKEND_DRIVER:-"fake"}
|
||||
DESIGNATE_POOL_ID=${DESIGNATE_POOL_ID:-794ccc2c-d751-44fe-b57f-8894c9f5c842}
|
||||
DESIGNATE_DEFAULT_NS_RECORD=${DESIGNATE_DEFAULT_NS_RECORD:-ns1.devstack.org.}
|
||||
DESIGNATE_NOTIFICATION_DRIVER=${DESIGNATE_NOTIFICATION_DRIVER:-messagingv2}
|
||||
@ -35,7 +34,6 @@ DESIGNATE_SERVICE_PROTOCOL=${DESIGNATE_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL}
|
||||
DESIGNATE_SERVICE_HOST=${DESIGNATE_SERVICE_HOST:-$SERVICE_HOST}
|
||||
DESIGNATE_SERVICE_PORT_DNS=${DESIGNATE_SERVICE_PORT_DNS:-53}
|
||||
DESIGNATE_SERVICE_PORT_MDNS=${DESIGNATE_SERVICE_PORT_MDNS:-5354}
|
||||
DESIGNATE_SERVICE_PORT_AGENT=${DESIGNATE_SERVICE_PORT_AGENT:-5358}
|
||||
|
||||
DESIGNATE_DIR=$DEST/designate
|
||||
# Default directories
|
||||
@ -82,5 +80,4 @@ enable_service designate-api
|
||||
enable_service designate-worker
|
||||
enable_service designate-producer
|
||||
enable_service designate-mdns
|
||||
enable_service designate-agent
|
||||
enable_service designate-sink
|
||||
|
@ -4,8 +4,8 @@ register_db_to_save designate
|
||||
devstack_localrc base enable_plugin designate https://opendev.org/openstack/designate
|
||||
devstack_localrc target enable_plugin designate https://opendev.org/openstack/designate
|
||||
|
||||
devstack_localrc base enable_service designate-api designate-central designate-producer designate-worker designate-mdns designate-agent designate-sink designate
|
||||
devstack_localrc target enable_service designate-api designate-central designate-producer designate-worker designate-mdns designate-agent designate-sink designate
|
||||
devstack_localrc base enable_service designate-api designate-central designate-producer designate-worker designate-mdns designate-sink designate
|
||||
devstack_localrc target enable_service designate-api designate-central designate-producer designate-worker designate-mdns designate-sink designate
|
||||
|
||||
BASE_RUN_SMOKE=False
|
||||
TARGET_RUN_SMOKE=False
|
||||
|
@ -19,12 +19,11 @@ set -o xtrace
|
||||
stop_process designate-central
|
||||
stop_process designate-api
|
||||
stop_process designate-mdns
|
||||
stop_process designate-agent
|
||||
stop_process designate-sink
|
||||
stop_process designate-worker
|
||||
stop_process designate-producer
|
||||
|
||||
|
||||
# sanity check that service is actually down
|
||||
ensure_services_stopped designate-api designate-central designate-mdns designate-agent designate-sink designate-worker designate-producer
|
||||
ensure_services_stopped designate-api designate-central designate-mdns designate-sink designate-worker designate-producer
|
||||
|
||||
|
@ -89,7 +89,6 @@ run_process "designate-api" "$(which uwsgi) --procname-prefix designate-api --in
|
||||
run_process designate-producer "$DESIGNATE_BIN_DIR/designate-producer --config-file $DESIGNATE_CONF"
|
||||
run_process designate-worker "$DESIGNATE_BIN_DIR/designate-worker --config-file $DESIGNATE_CONF"
|
||||
run_process designate-mdns "$DESIGNATE_BIN_DIR/designate-mdns --config-file $DESIGNATE_CONF"
|
||||
run_process designate-agent "$DESIGNATE_BIN_DIR/designate-agent --config-file $DESIGNATE_CONF"
|
||||
run_process designate-sink "$DESIGNATE_BIN_DIR/designate-sink --config-file $DESIGNATE_CONF"
|
||||
restart_apache_server
|
||||
|
||||
@ -103,7 +102,7 @@ if ! timeout $SERVICE_TIMEOUT sh -c "while ! wget --no-proxy -q -O- $DESIGNATE_S
|
||||
fi
|
||||
|
||||
# Don't succeed unless the service come up
|
||||
ensure_services_started designate-api designate-central designate-producer designate-worker designate-mdns designate-agent designate-sink
|
||||
ensure_services_started designate-api designate-central designate-producer designate-worker designate-mdns designate-sink
|
||||
|
||||
set +o xtrace
|
||||
echo "*********************************************************************"
|
||||
|
@ -30,7 +30,6 @@ from sphinx.util import logging
|
||||
from sphinx.util.osutil import copyfile
|
||||
|
||||
from designate.backend.base import Backend
|
||||
from designate.backend.agent_backend.base import AgentBackend
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -128,8 +127,6 @@ class SupportMatrixDirective(rst.Directive):
|
||||
except config_parser.NoOptionError:
|
||||
if cfg.get("backends.%s" % item, "type") == "xfr":
|
||||
backend = Backend.get_driver(name[0])
|
||||
elif cfg.get("backends.%s" % item, "type") == "agent":
|
||||
backend = AgentBackend.get_driver(name[0])
|
||||
status = backend.__backend_status__
|
||||
|
||||
if len(name) == 1:
|
||||
|
@ -1,45 +0,0 @@
|
||||
..
|
||||
Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Agent Backend
|
||||
=============
|
||||
|
||||
This page documents using the various Agent backends, and it's accompanying
|
||||
service, `designate-agent`. This backend uses an extension of the DNS protocol
|
||||
itself to send management requests to the remote agent processes, where the
|
||||
requests will be actioned.
|
||||
|
||||
The `rpc` traffic between designate and the `agent` is both unauthenticated and
|
||||
unencrypted. Do not run this traffic over unsecured networks.
|
||||
|
||||
Designate Configuration
|
||||
-----------------------
|
||||
|
||||
For each designate-agent running, add a target to the pools.yaml configuration
|
||||
file, using the following template:
|
||||
|
||||
.. literalinclude:: sample_yaml_snippets/agent.yaml
|
||||
:language: yaml
|
||||
|
||||
Then update the designate pools database using the ``designate-manage pool``
|
||||
command - see :ref:`designate_manage_pool` for further details on the
|
||||
``designate-manage pool`` command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ designate-manage pool update
|
||||
|
||||
.. TODO: Document how to configure the agent service itself, and the available
|
||||
agent backends.
|
@ -1,134 +0,0 @@
|
||||
..
|
||||
Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
|
||||
Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Djbdns Agent backend
|
||||
********************
|
||||
|
||||
|
||||
Djbdns User documentation
|
||||
=========================
|
||||
|
||||
This page documents the Agent backend for `djbdns <https://cr.yp.to/djbdns.html>`_.
|
||||
|
||||
The agent runs on the same host as the `tinydns <https://cr.yp.to/djbdns/tinydns.html>`_ resolver.
|
||||
It receives DNS messages from Mini DNS using private DNS OPCODEs
|
||||
and classes and creates or deletes zones in the data.cdb file using
|
||||
`axfr-get <https://cr.yp.to/djbdns/axfr-get.html>`_ and
|
||||
`tinydns-data <https://cr.yp.to/djbdns/tinydns-data.html>`_
|
||||
|
||||
Setting up Djbdns on Ubuntu Trusty
|
||||
------------------------------------
|
||||
|
||||
Assuming no DNS resolver is already installed, run as root:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
set -u
|
||||
datadir=/var/lib/djbdns
|
||||
ug_name=djbdns
|
||||
tinydns_ipaddr=127.0.0.1
|
||||
|
||||
[[ -d $datadir ]] && echo "$datadir already exists" && exit 1
|
||||
set -e
|
||||
apt-get update
|
||||
apt-get install dbndns daemontools
|
||||
if ! getent passwd $ug_name >/dev/null; then
|
||||
adduser --quiet --system --group --no-create-home --home /nonexistent $ug_name
|
||||
fi
|
||||
tinydns-conf $ug_name $ug_name $datadir $tinydns_ipaddr
|
||||
cd $datadir/root
|
||||
tinydns-data data
|
||||
chown -Rv $ug_name:$ug_name $datadir
|
||||
|
||||
Setup the a Systemd service or, alternatively, an initfile to start TinyDNS.
|
||||
|
||||
In the contrib/djbdns directory there are example files for both.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
systemctl daemon-reload
|
||||
service tinydns start
|
||||
service tinydns status
|
||||
|
||||
|
||||
If needed, create the rootwrap filters, as root:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cat > /etc/designate/rootwrap.d/djbdns.filters <<EOF
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
[Filters]
|
||||
tcpclient: CommandFilter, /usr/bin/tcpclient, root
|
||||
axfr-get: CommandFilter, /usr/bin/axfr-get, root
|
||||
EOF
|
||||
|
||||
# Check the filter:
|
||||
sudo /usr/local/bin/designate-rootwrap /etc/designate/rootwrap.conf tcpclient -h
|
||||
sudo /usr/local/bin/designate-rootwrap /etc/designate/rootwrap.conf axfr-get -h
|
||||
|
||||
Configure the "service.agent" and "backend.agent.djbdns"
|
||||
sections in /etc/designate/designate.conf
|
||||
|
||||
Look in designate.conf.example for examples.
|
||||
|
||||
Create an agent pool:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Fetch the existing pool(s) if needed or start from scratch
|
||||
designate-manage pool generate_file --file /tmp/pool.yaml
|
||||
# Edit the file (see below) and reload it as:
|
||||
designate-manage pool update --file /tmp/pool.yaml
|
||||
|
||||
The "targets" section in pool.yaml should look like:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
targets:
|
||||
- description: gdnsd agent
|
||||
masters:
|
||||
- host: <MiniDNS IP addr>
|
||||
port: 5354
|
||||
options: {}
|
||||
options:
|
||||
- host: <Agent IP addr>
|
||||
port: 5358
|
||||
type: agent
|
||||
|
||||
|
||||
Testing
|
||||
^^^^^^^
|
||||
|
||||
Create new zones and records. Monitor the agent logfile and the contents of the
|
||||
TinyDNS datadir. The data.cdb file should be receiving updates.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
openstack zone create --email example@example.org example.org.
|
||||
openstack recordset create example.org. --type A foo --records 1.2.3.4
|
||||
dig example.org @<tinydns_ipaddr> SOA
|
||||
dig foo.example.org @<tinydns_ipaddr> A
|
||||
|
||||
Developer documentation
|
||||
=======================
|
||||
|
||||
Devstack testbed
|
||||
----------------
|
||||
|
||||
Follow "Setting up Djbdns on Ubuntu Trusty"
|
||||
|
||||
Configure Tinydns to do AXFR from MiniDNS on 192.168.121.131
|
@ -1,120 +0,0 @@
|
||||
..
|
||||
Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
|
||||
Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
gdnsd Agent backend
|
||||
*******************
|
||||
|
||||
|
||||
User documentation
|
||||
==================
|
||||
|
||||
This page documents the Agent backend for `gdnsd <http://gdnsd.org/>`_.
|
||||
|
||||
The agent runs on the same host as the resolver. It receives DNS messages
|
||||
from Mini DNS using private DNS OPCODEs and classes and
|
||||
creates/updates/deletes zones on gdnsd using zone files under
|
||||
the gdnsd configuration directory.
|
||||
|
||||
The backend supports gdnsd from version 2.0
|
||||
|
||||
`gdnsd documentation <https://github.com/gdnsd/gdnsd/wiki>`_
|
||||
|
||||
Setting up gdnsd on Ubuntu Vivid
|
||||
--------------------------------
|
||||
|
||||
Run as root:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
apt-get update
|
||||
apt-get install gdnsd
|
||||
|
||||
Configuring gdnsd
|
||||
-----------------
|
||||
|
||||
Assuming gdnsd has been freshly installed on the system, run as root:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Monitor syslog during the next steps
|
||||
tail -f /var/log/syslog
|
||||
|
||||
# config check should be successful
|
||||
/usr/sbin/gdnsd checkconf
|
||||
|
||||
# Start the daemon if needed
|
||||
service gdnsd status
|
||||
service gdnsd start
|
||||
|
||||
# gdnsd should be listening on TCP and UDP ports
|
||||
netstat -lnptu | grep '/gdnsd'
|
||||
|
||||
# Test the daemon: it should respond with "gdnsd"
|
||||
dig @127.0.0.1 CH TXT +short
|
||||
|
||||
Configure the "service.agent" and "backend.agent.gdnsd" sections
|
||||
in /etc/designate/designate.conf
|
||||
|
||||
Look in designate.conf.example for more complete examples
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[service:agent]
|
||||
backend_driver = gdnsd
|
||||
# Place here the MiniDNS ipaddr and port (not the agent itself)
|
||||
masters = 192.168.27.100:5354
|
||||
|
||||
[backend:agent:gdnsd]
|
||||
#gdnsd_cmd_name = gdnsd
|
||||
#confdir_path = /etc/gdnsd
|
||||
#query_destination = 127.0.0.1
|
||||
|
||||
Ensure that the "zones" directory under "confdir_path" (default /etc/gdnsd)
|
||||
is readable and writable by the system user running the Designate Agent
|
||||
|
||||
Create an agent pool:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Fetch the existing pool(s) if needed
|
||||
designate-manage pool generate_file --file /tmp/pool.yaml
|
||||
# Edit the file (see below) and reload it as:
|
||||
designate-manage pool update --file /tmp/pool.yaml
|
||||
|
||||
The "targets" section in pool.yaml should look like:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
targets:
|
||||
- description: gdnsd agent
|
||||
masters:
|
||||
- host: <MiniDNS IP addr>
|
||||
port: 5354
|
||||
options: {}
|
||||
options:
|
||||
- host: <Agent IP addr>
|
||||
port: 5358
|
||||
type: agent
|
||||
|
||||
Start the Designate Agent. You should see log messages similar to:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
2016-05-03 15:13:38.193 INFO designate.backend.agent_backend.impl_gdnsd [-] gdnsd command: 'gdnsd'
|
||||
2016-05-03 15:13:38.193 INFO designate.backend.agent_backend.impl_gdnsd [-] gdnsd conf directory: '/etc/gdnsd'
|
||||
2016-05-03 15:13:38.194 INFO designate.backend.agent_backend.impl_gdnsd [-] Resolvers: ['127.0.0.1']
|
||||
|
@ -1,189 +0,0 @@
|
||||
..
|
||||
Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
|
||||
Author: Federico Ceratto <federico.ceratto@hpe.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
Knot DNS 2 Agent backend
|
||||
************************
|
||||
|
||||
|
||||
Knot DNS 2 User documentation
|
||||
=============================
|
||||
|
||||
This page documents the Agent backend for `Knot DNS <https://www.knot-dns.cz/>`_.
|
||||
|
||||
The agent runs on the same host as the resolver. It receives DNS messages from
|
||||
Mini DNS using private DNS OPCODEs and classes and creates or deletes zones
|
||||
on Knot using the knotc tool. It also instructs Knot to request AXFR
|
||||
from MiniDNS when a zone is created or updated.
|
||||
|
||||
Support matrix:
|
||||
|
||||
* 2.0 and older: not supported
|
||||
* 2.2.0: `affected by a bug <https://gitlab.labs.nic.cz/labs/knot/issues/460>`_
|
||||
|
||||
|
||||
`Knot DNS documentation <https://www.knot-dns.cz/documentation/>`_
|
||||
|
||||
Configuring Knot DNS
|
||||
--------------------
|
||||
|
||||
Assuming Knot has been freshly installed on the system, run as root:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Monitor syslog during the next steps
|
||||
tail -f /var/log/syslog
|
||||
|
||||
# Start the daemon, ensure it's running
|
||||
service knot start
|
||||
netstat -npltu | grep knotd
|
||||
|
||||
# Create the config database
|
||||
knotc conf-init
|
||||
|
||||
# Edit /etc/default/knot
|
||||
# Set the variable:
|
||||
# KNOTD_ARGS="-C /var/lib/knot/confdb"
|
||||
|
||||
# Restart
|
||||
service knot restart
|
||||
|
||||
# Check if the deamon is still running from the conf file in /etc/knot/
|
||||
ps axuw | grep knotd
|
||||
|
||||
# if so, apply this workaround for bug
|
||||
# https://gitlab.labs.nic.cz/labs/knot/issues/455
|
||||
( cd /etc/default/ && ln -s knot knotd )
|
||||
service knot restart
|
||||
ps axuw | grep knotd
|
||||
|
||||
# Ensure the confdb is present
|
||||
test -f /var/lib/knot/confdb/data.mdb && echo OK
|
||||
|
||||
# Create the configuration
|
||||
# Populate the variable with the MiniDNS ipaddr:
|
||||
MINIDNS_IPADDR=
|
||||
|
||||
knotc conf-begin
|
||||
knotc conf-set server.listen 0.0.0.0@53
|
||||
# To listen on IPv6 as well, also run this:
|
||||
# knotc conf-set server.listen '::@53'
|
||||
knotc conf-set remote[minidns]
|
||||
knotc conf-set remote[minidns].address $MINIDNS_IPADDR@5354
|
||||
knotc conf-set template[default]
|
||||
knotc conf-set template[default].master minidns
|
||||
knotc conf-set template[default].acl acl_minidns
|
||||
knotc conf-set template[default].semantic-checks on
|
||||
knotc conf-set zone[example.com]
|
||||
knotc conf-set log.any info
|
||||
knotc conf-set log.target syslog
|
||||
knotc conf-set acl[acl_minidns]
|
||||
knotc conf-set acl[acl_minidns].address $MINIDNS_IPADDR
|
||||
knotc conf-set acl[acl_minidns].action notify
|
||||
# Review the changes and commit
|
||||
knotc conf-diff
|
||||
knotc conf-commit
|
||||
|
||||
# Optionally check and back up the conf
|
||||
knotc conf-check
|
||||
knotc conf-export knot.conf.bak && cat knot.conf.bak
|
||||
|
||||
# Ensure the zone survives a restart
|
||||
service knot restart
|
||||
knotc zone-status example.com
|
||||
|
||||
# Test Knot: this should return the version
|
||||
dig @127.0.0.1 version.server CH TXT
|
||||
|
||||
If needed, create a rootwrap filter, as root:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cat > /etc/designate/rootwrap.d/knot2.filters <<EOF
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
[Filters]
|
||||
knotc: CommandFilter, /usr/sbin/knotc, root
|
||||
EOF
|
||||
|
||||
# Check the filter:
|
||||
sudo /usr/local/bin/designate-rootwrap /etc/designate/rootwrap.conf knotc status
|
||||
|
||||
Configure the "service.agent" and "backend.agent.knot2" sections
|
||||
in /etc/designate/designate.conf
|
||||
|
||||
Look in designate.conf.example for examples
|
||||
|
||||
Create an agent pool:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Fetch the existing pool(s) if needed or start from scratch
|
||||
designate-manage pool generate_file --file /tmp/pool.yaml
|
||||
# Edit the file (see below) and reload it as:
|
||||
designate-manage pool update --file /tmp/pool.yaml
|
||||
|
||||
The "targets" section in pool.yaml should look like:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
targets:
|
||||
- description: knot2 agent
|
||||
masters:
|
||||
- host: <MiniDNS IP addr>
|
||||
port: 5354
|
||||
options: {}
|
||||
options:
|
||||
- host: <Agent IP addr>
|
||||
port: 5358
|
||||
type: agent
|
||||
|
||||
Developer documentation
|
||||
=======================
|
||||
|
||||
Devstack testbed
|
||||
----------------
|
||||
|
||||
Follow "Setting up Knot DNS on Ubuntu Trusty"
|
||||
|
||||
Configure Knot to slave from MiniDNS on 192.168.121.131
|
||||
|
||||
Knotd configuration example (sudo knotc conf-export <filename>):
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Configuration export (Knot DNS 2.1.1)
|
||||
|
||||
server:
|
||||
listen: "0.0.0.0@53"
|
||||
|
||||
log:
|
||||
- target: "syslog"
|
||||
any: "debug"
|
||||
|
||||
acl:
|
||||
- id: "acl_minidns"
|
||||
address: [ "192.168.121.131" ]
|
||||
action: [ "notify" ]
|
||||
|
||||
remote:
|
||||
- id: "minidns"
|
||||
address: "192.168.121.131@5354"
|
||||
|
||||
template:
|
||||
- id: "default"
|
||||
master: "minidns"
|
||||
acl: "acl_minidns"
|
||||
semantic-checks: "on"
|
@ -1,137 +0,0 @@
|
||||
..
|
||||
Copyright 2016 Cloudbase Solutions Srl
|
||||
|
||||
Author: Alin Balutoiu <abalutoiu@cloudbasesolutions.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
MSDNS Agent Backend
|
||||
*******************
|
||||
|
||||
MSDNS User Documentation
|
||||
========================
|
||||
|
||||
This page documents using the MSDNS Agent backend.
|
||||
|
||||
The agent runs on the Windows host where the Microsoft DNS Server feature
|
||||
is installed. It receives DNS messages from Mini DNS using private
|
||||
DNS OPCODEs and classes and creates or deletes zones using WMI calls.
|
||||
|
||||
It also instructs MSDNS to request AXFR from MiniDNS when a zone is created
|
||||
or updated.
|
||||
|
||||
`Microsoft DNS documentation for managing DNS zones
|
||||
<https://msdn.microsoft.com/en-us/library/windows/desktop/ms682757.aspx>`_
|
||||
|
||||
Setting up the Microsoft DNS server on Windows Server
|
||||
-----------------------------------------------------
|
||||
|
||||
The DNS Server role can be installed on the system by following the
|
||||
documentation available here:
|
||||
`How to install the DNS Server role
|
||||
<https://technet.microsoft.com/en-us/library/cc725925.aspx>`_
|
||||
|
||||
Configuring MSDNS
|
||||
-----------------
|
||||
|
||||
Assuming the DNS Server role has been installed on the system, follow the
|
||||
next steps to complete the configuration.
|
||||
|
||||
These steps are for the Windows host which will run the designate agent.
|
||||
Make sure that Python 2.7 or Python 3.4 is installed on the system already.
|
||||
|
||||
To install Designate, clone the repository from https://github.com/openstack/designate
|
||||
and do a pip install. Example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
git clone https://github.com/openstack/designate
|
||||
pip install .\\designate
|
||||
|
||||
After that, we need to configure the Designate Agent.
|
||||
Inside the github repository, there is a folder named "etc/designate"
|
||||
which can be used as default configuration.
|
||||
|
||||
Copy the folder somewhere else, for this example we will copy it to
|
||||
C:\\etc\\designate
|
||||
Inside the configuration folder, make a copy of designate.conf.sample
|
||||
and rename the copy to designate.conf
|
||||
Example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
copy C:\\etc\\designate\\designate.conf.sample C:\\etc\\designate\\designate.conf
|
||||
|
||||
|
||||
Configure the "service.agent" and "backend.agent.msdns" sections in
|
||||
C:\\etc\\designate\\designate.conf
|
||||
|
||||
Look in C:\\etc\\designate\\designate.conf.example for more complete examples.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[service:agent]
|
||||
backend_driver = msdns
|
||||
# Place here the MiniDNS ipaddr and port (no the agent itself)
|
||||
masters = <MiniDNS IP addr>:53
|
||||
|
||||
Ensure that "policy_file" under the [default] section is set:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
policy_file = C:\\etc\\designate\\policy.yaml
|
||||
|
||||
Start the designate agent using
|
||||
(Python 2.7 was installed in the default location C:\\Python27):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
C:\\Python27\\Scripts\\designate-agent.exe --config-file 'C:\\etc\\designate\\designate.conf'
|
||||
|
||||
You should see log messages similar to:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
2016-06-22 02:00:47.177 3436 INFO designate.backend.agent_backend.impl_msdns [-] Started msdns backend
|
||||
2016-06-22 02:00:47.177 3436 INFO designate.service [-] _handle_tcp thread started
|
||||
2016-06-22 02:00:47.177 3436 INFO designate.service [-] _handle_udp thread started
|
||||
|
||||
|
||||
The following steps are for the system running the Designate controller.
|
||||
|
||||
Make sure to set the mDNS port to 53 in the ``[service:mdns]`` section.
|
||||
MS DNS does not support Masters that are on any port other than 53.
|
||||
|
||||
Create an agent pool:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Fetch the existing pool(s) if needed or start from scratch
|
||||
designate-manage pool generate_file --file /tmp/pool.yaml
|
||||
# Edit the file (see below) and reload it as:
|
||||
designate-manage pool update --file /tmp/pool.yaml
|
||||
|
||||
The "targets" section in pool.yaml should look like:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
targets:
|
||||
- description: Microsoft DNS agent
|
||||
masters:
|
||||
- host: <MiniDNS IP addr>
|
||||
port: 53
|
||||
options: {}
|
||||
options:
|
||||
- host: <Agent IP addr>
|
||||
port: 5358
|
||||
type: agent
|
@ -1,15 +0,0 @@
|
||||
targets:
|
||||
- type: agent
|
||||
description: Agent Server 1
|
||||
|
||||
# List out the designate-mdns servers from which Agent servers should
|
||||
# request zone transfers (AXFRs) from.
|
||||
masters:
|
||||
- host: 192.0.2.1
|
||||
port: 5354
|
||||
|
||||
# Agent Configuration options, this should be this targets
|
||||
# designate-agent service's host and port.
|
||||
options:
|
||||
host: 192.0.2.2
|
||||
port: 5358
|
@ -72,10 +72,6 @@ DynDNS or even another Designate instance. In this situation, you will
|
||||
typically have a single target with a set of nameservers to test that
|
||||
meet your requirements.
|
||||
|
||||
Yet another example is when using a Designate agent. In this scenario
|
||||
your agent instances are the targets and the nameservers the agent
|
||||
updates would be checked for the correct information.
|
||||
|
||||
Managing Pools
|
||||
==============
|
||||
|
||||
|
@ -53,14 +53,6 @@ backend-impl-akamai_v2=Akamai DNS v2
|
||||
backend-impl-infoblox-xfr=Infoblox (XFR)
|
||||
backend-impl-nsd4=NSD4
|
||||
backend-impl-ns1=NS1 DNS
|
||||
backend-impl-agent=Agent
|
||||
backend-impl-bind9-agent=Bind9 (Agent)
|
||||
backend-impl-denominator=Denominator
|
||||
backend-impl-knot2-agent=Knot2 (Agent)
|
||||
backend-impl-djbdns-agent=Djbdns (Agent)
|
||||
backend-impl-gdnsd-agent=Gdnsd (Agent)
|
||||
backend-impl-msdns-agent=Microsoft DNS (Agent)
|
||||
|
||||
|
||||
[backends.backend-impl-bind9]
|
||||
docs=bind9_backend_docs
|
||||
@ -87,36 +79,12 @@ docs=ns1_backend_docs
|
||||
status=untested
|
||||
config=backends/sample_yaml_snippets/ns1.yaml
|
||||
|
||||
[backends.backend-impl-agent]
|
||||
|
||||
[backends.backend-impl-bind9-agent]
|
||||
type=agent
|
||||
|
||||
[backends.backend-impl-knot2-agent]
|
||||
type=agent
|
||||
status=experimental
|
||||
|
||||
[backends.backend-impl-djbdns-agent]
|
||||
type=agent
|
||||
status=experimental
|
||||
|
||||
[backends.backend-impl-gdnsd-agent]
|
||||
type=agent
|
||||
status=experimental
|
||||
|
||||
[backends.backend-impl-infoblox-xfr]
|
||||
status=untested
|
||||
maintainers=Infoblox OpenStack Team <openstack-maintainer@infoblox.com>
|
||||
|
||||
[backends.backend-impl-nsd4]
|
||||
|
||||
[backends.backend-impl-denominator]
|
||||
type=agent
|
||||
|
||||
[backends.backend-impl-msdns-agent]
|
||||
type=agent
|
||||
status=untested
|
||||
|
||||
[grades]
|
||||
valid-grades=integrated,master-compatible,release-compatible,untested,failing,known-broken,experimental,deprecated,end-of-life
|
||||
|
||||
|
@ -45,9 +45,6 @@ The default values are:
|
||||
| Component | Protocol | Port |
|
||||
| (header rows optional) | | numbers |
|
||||
+========================+============+==========+
|
||||
| Agent | TCP | 5358 |
|
||||
+ +------------+----------+
|
||||
| | UDP | 5358 |
|
||||
+------------------------+------------+----------+
|
||||
| API | TCP | 9001 |
|
||||
+------------------------+------------+----------+
|
||||
|
@ -223,14 +223,6 @@ GMR Example
|
||||
==== Configuration ====
|
||||
========================================================================
|
||||
|
||||
backend:agent:bind9:
|
||||
query-destination = 127.0.0.1
|
||||
rndc-config-file = None
|
||||
rndc-host = 127.0.0.1
|
||||
rndc-key-file = None
|
||||
rndc-port = 953
|
||||
zone-file-path = /opt/stack/data/designate/zones
|
||||
|
||||
backend:bind9:
|
||||
masters =
|
||||
127.0.0.1:5354
|
||||
|
@ -67,43 +67,3 @@ Backend PowerDNS 4
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Agent Backend KnotDNS
|
||||
=====================
|
||||
|
||||
.. automodule:: designate.backend.agent_backend.impl_knot2
|
||||
:members:
|
||||
:special-members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Agent Backend gdnsd
|
||||
===================
|
||||
|
||||
.. automodule:: designate.backend.agent_backend.impl_gdnsd
|
||||
:members:
|
||||
:special-members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Agent Backend Djbdns
|
||||
====================
|
||||
|
||||
.. automodule:: designate.backend.agent_backend.impl_djbdns
|
||||
:members:
|
||||
:special-members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Agent Backend MSDNS
|
||||
====================
|
||||
|
||||
.. automodule:: designate.backend.agent_backend.impl_msdns
|
||||
:members:
|
||||
:special-members:
|
||||
:private-members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
@ -30,14 +30,6 @@ The DNS service consists of the following components:
|
||||
the customer facing DNS Servers. Can also pull in DNS information about
|
||||
DNS Zones hosted outside of the Designate infrastructure
|
||||
|
||||
``designate-agent`` component
|
||||
A small python daemon that can be used for a limited sub set of DNS Servers
|
||||
Some DNS Servers requrire commands be run locally, and to do this we use
|
||||
this component.
|
||||
|
||||
.. note:: The majority of the DNS service installs will not need this
|
||||
component.
|
||||
|
||||
``Customer Facing DNS Servers``
|
||||
Serves DNS requests to end users. They are orchestreated by the
|
||||
``designate-worker``, and the supported list is maintained
|
||||
|
@ -24,7 +24,6 @@ Verify operation of the DNS service.
|
||||
|
||||
../usr/bin/python /usr/bin/designate-mdns --config-file /etc/designate/designate.conf
|
||||
../usr/bin/python /usr/bin/designate-central --config-file /etc/designate/designate.conf
|
||||
../usr/bin/python /usr/bin/designate-agent --config-file /etc/designate/designate.conf
|
||||
../usr/bin/python /usr/bin/designate-api --config-file /etc/designate/designate.conf
|
||||
../usr/bin/python /usr/bin/designate-worker --config-file /etc/designate/designate.conf
|
||||
../usr/bin/python /usr/bin/designate-producer --config-file /etc/designate/designate.conf
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
prelude: >
|
||||
Designate-agent removal is complete in this version of designate.
|
||||
upgrade:
|
||||
- |
|
||||
The agent and all agent backends has been removed and can no longer be used.
|
13
setup.cfg
13
setup.cfg
@ -36,7 +36,6 @@ data_files =
|
||||
oslo.config.opts =
|
||||
designate.conf = designate.conf.opts:list_opts
|
||||
|
||||
|
||||
oslo.config.opts.defaults =
|
||||
designate.conf = designate.common.config:set_defaults
|
||||
|
||||
@ -53,12 +52,10 @@ console_scripts =
|
||||
designate-manage = designate.cmd.manage:main
|
||||
designate-mdns = designate.cmd.mdns:main
|
||||
designate-sink = designate.cmd.sink:main
|
||||
designate-agent = designate.cmd.agent:main
|
||||
designate-worker = designate.cmd.worker:main
|
||||
designate-producer = designate.cmd.producer:main
|
||||
designate-status = designate.cmd.status:main
|
||||
|
||||
|
||||
designate.api.admin.extensions =
|
||||
reports = designate.api.admin.controllers.extensions.reports:ReportsController
|
||||
quotas = designate.api.admin.controllers.extensions.quotas:QuotasController
|
||||
@ -78,18 +75,8 @@ designate.backend =
|
||||
nsd4 = designate.backend.impl_nsd4:NSD4Backend
|
||||
infoblox = designate.backend.impl_infoblox:InfobloxBackend
|
||||
fake = designate.backend.impl_fake:FakeBackend
|
||||
agent = designate.backend.agent:AgentPoolBackend
|
||||
ns1 = designate.backend.impl_ns1:NS1Backend
|
||||
|
||||
designate.backend.agent_backend =
|
||||
bind9 = designate.backend.agent_backend.impl_bind9:Bind9Backend
|
||||
knot2 = designate.backend.agent_backend.impl_knot2:Knot2Backend
|
||||
djbdns = designate.backend.agent_backend.impl_djbdns:DjbdnsBackend
|
||||
denominator = designate.backend.agent_backend.impl_denominator:DenominatorBackend
|
||||
fake = designate.backend.agent_backend.impl_fake:FakeBackend
|
||||
gdnsd = designate.backend.agent_backend.impl_gdnsd:GdnsdBackend
|
||||
msdns = designate.backend.agent_backend.impl_msdns:MSDNSBackend
|
||||
|
||||
designate.network_api =
|
||||
fake = designate.network_api.fake:FakeNetworkAPI
|
||||
neutron = designate.network_api.neutron:NeutronNetworkAPI
|
||||
|
Loading…
Reference in New Issue
Block a user