Merge "Removed RPC calls from MDNS and moved them to the Worker"

This commit is contained in:
Zuul 2022-06-25 05:58:17 +00:00 committed by Gerrit Code Review
commit d05232fc07
44 changed files with 755 additions and 1296 deletions

View File

@ -185,7 +185,7 @@ class RequestHandler(object):
try:
zone = dnsutils.do_axfr(zone_name, self.masters,
source=self.transfer_source)
source=self.transfer_source)
self.backend.update_zone(zone)
except Exception:
response.set_rcode(dns.rcode.from_text("SERVFAIL"))

View File

@ -40,7 +40,7 @@ from designate.backend import private_codes
from designate.conf.agent import DEFAULT_AGENT_PORT
from designate import dnsutils
from designate import exceptions
from designate.mdns import rpcapi as mdns_api
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -59,10 +59,6 @@ class AgentPoolBackend(base.Backend):
self.max_retries = CONF['service:worker'].poll_max_retries
# FIXME: the agent retries creating zones without any interval
@property
def mdns_api(self):
return mdns_api.MdnsAPI.get_instance()
def create_zone(self, context, zone):
LOG.debug('Create Zone')
response = self._make_and_send_dns_message(

View File

@ -19,7 +19,6 @@ from oslo_config import cfg
from oslo_log import log as logging
from designate.context import DesignateContext
from designate.mdns import rpcapi as mdns_api
from designate.plugin import DriverPlugin
LOG = logging.getLogger(__name__)
@ -58,10 +57,6 @@ class Backend(DriverPlugin):
def stop(self):
LOG.info('Stopped %s backend', self.get_canonical_name())
@property
def mdns_api(self):
return mdns_api.MdnsAPI.get_instance()
# Core Backend Interface
@abc.abstractmethod
def create_zone(self, context, zone):

View File

@ -37,7 +37,6 @@ from designate import context as dcontext
from designate import coordination
from designate import dnsutils
from designate import exceptions
from designate.mdns import rpcapi as mdns_rpcapi
from designate import network_api
from designate import notifications
from designate import objects
@ -243,10 +242,6 @@ class Service(service.RPCService):
self.coordination.stop()
super(Service, self).stop(graceful)
@property
def mdns_api(self):
return mdns_rpcapi.MdnsAPI.get_instance()
@property
def worker_api(self):
return worker_rpcapi.WorkerAPI.get_instance()
@ -957,7 +952,7 @@ class Service(service.RPCService):
self.worker_api.create_zone(context, zone)
if zone.type == 'SECONDARY':
self.mdns_api.perform_zone_xfr(context, zone)
self.worker_api.perform_zone_xfr(context, zone)
# If zone is a superzone, update subzones
# with new parent IDs
@ -1116,7 +1111,7 @@ class Service(service.RPCService):
# Fire off a XFR
if 'masters' in changes:
self.mdns_api.perform_zone_xfr(context, zone)
self.worker_api.perform_zone_xfr(context, zone)
self.worker_api.update_zone(context, zone)
@ -1239,15 +1234,15 @@ class Service(service.RPCService):
# Ensure the format of the servers are correct, then poll the
# serial
srv = random.choice(zone.masters)
status, serial, retries = self.mdns_api.get_serial_number(
context, zone, srv.host, srv.port, 3, 1, 3, 0)
status, serial = self.worker_api.get_serial_number(
context, zone, srv.host, srv.port)
# Perform XFR if serial's are not equal
if serial > zone.serial:
if serial is not None and serial > zone.serial:
LOG.info("Serial %(srv_serial)d is not equal to zone's "
"%(serial)d, performing AXFR",
{"srv_serial": serial, "serial": zone.serial})
self.mdns_api.perform_zone_xfr(context, zone)
self.worker_api.perform_zone_xfr(context, zone)
@rpc.expected_exceptions()
def count_zones(self, context, criterion=None):

View File

@ -28,7 +28,6 @@ from designate.conf import infoblox
from designate.conf import keystone
from designate.conf import knot2
from designate.conf import mdns
from designate.conf import metrics
from designate.conf import msdns
from designate.conf import network_api
from designate.conf import producer
@ -54,7 +53,6 @@ infoblox.register_opts(CONF)
keystone.register_opts(CONF)
knot2.register_opts(CONF)
mdns.register_opts(CONF)
metrics.register_opts(CONF)
msdns.register_opts(CONF)
network_api.register_opts(CONF)
producer.register_opts(CONF)

View File

@ -33,8 +33,12 @@ MDNS_OPTS = [
help='mDNS TCP Backlog'),
cfg.FloatOpt('tcp_recv_timeout', default=0.5,
help='mDNS TCP Receive Timeout'),
cfg.BoolOpt('all_tcp', default=False,
help='Send all traffic over TCP'),
cfg.IntOpt('all_tcp', help='Send all traffic over TCP',
default=None,
deprecated_for_removal=True,
deprecated_reason='This parameter should now be configured in'
'service:worker instead',
deprecated_since='Zed'),
cfg.BoolOpt('query_enforce_tsig', default=False,
help='Enforce all incoming queries (including AXFR) are TSIG '
'signed'),
@ -45,7 +49,11 @@ MDNS_OPTS = [
cfg.StrOpt('topic', default='mdns',
help='RPC topic name for mdns'),
cfg.IntOpt('xfr_timeout', help="Timeout in seconds for XFR's.",
default=10),
default=None,
deprecated_for_removal=True,
deprecated_reason='This parameter should now be configured in'
'service:worker instead',
deprecated_since='Zed'),
]

View File

@ -1,36 +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.
from oslo_config import cfg
METRICS_GROUP = cfg.OptGroup(
name='monasca:statsd',
title="Configuration for Monasca Statsd"
)
METRICS_OPTS = [
cfg.BoolOpt('enabled', default=False, help='enable'),
cfg.IntOpt('port', default=8125, help='UDP port'),
cfg.StrOpt('hostname', default='127.0.0.1', help='hostname'),
]
def register_opts(conf):
conf.register_group(METRICS_GROUP)
conf.register_opts(METRICS_OPTS, group=METRICS_GROUP)
def list_opts():
return {
METRICS_GROUP: METRICS_OPTS
}

View File

@ -46,6 +46,21 @@ WORKER_OPTS = [
help='Whether to allow synchronous zone exports'),
cfg.StrOpt('topic', default='worker',
help='RPC topic name for worker'),
cfg.IntOpt('xfr_timeout', help="Timeout in seconds for XFR's.",
default=10),
cfg.IntOpt('serial_max_retries',
help='The maximum number of times to retry fetching a zones '
'serial.',
default=3),
cfg.IntOpt('serial_retry_delay',
help='The time to wait before retrying a zone serial request.',
default=1),
cfg.IntOpt('serial_timeout',
help='Timeout in seconds before giving up on fetching a zones '
'serial.',
default=1),
cfg.BoolOpt('all_tcp', default=False,
help='Send all traffic over TCP'),
]

View File

@ -337,19 +337,25 @@ def dnspythonrecord_to_recordset(rname, rdataset):
return rrset
def do_axfr(zone_name, servers, timeout=None, source=None):
def xfr_timeout():
if CONF['service:mdns'].xfr_timeout is not None:
return CONF['service:mdns'].xfr_timeout
else:
return CONF['service:worker'].xfr_timeout
def do_axfr(zone_name, servers, source=None):
"""
Requests an AXFR for a given zone name and process the response
:returns: Zone instance from dnspython
"""
random.shuffle(servers)
timeout = timeout or CONF["service:mdns"].xfr_timeout
xfr = None
for srv in servers:
for address in get_ip_addresses(srv['host']):
to = eventlet.Timeout(timeout)
to = eventlet.Timeout(xfr_timeout())
log_info = {'name': zone_name, 'host': srv, 'address': address}
try:
LOG.info(
@ -415,6 +421,24 @@ def notify(zone_name, host, port=53):
return send_dns_message(msg, host, port=port)
def soa(zone_name, host, port=53, timeout=10):
"""
Set up a soa packet and send it
"""
msg = prepare_msg(zone_name, rdatatype=dns.rdatatype.SOA,
dns_opcode=dns.opcode.QUERY)
msg.flags |= dns.flags.RD
return send_dns_message(msg, host, port=port, timeout=timeout)
def use_all_tcp():
if CONF['service:mdns'].all_tcp is not None:
return CONF['service:mdns'].all_tcp
else:
return CONF['service:worker'].all_tcp
def send_dns_message(dns_message, host, port=53, timeout=10):
"""
Send the dns message and return the response
@ -423,7 +447,7 @@ def send_dns_message(dns_message, host, port=53, timeout=10):
"""
ip_address = get_ip_address(host)
# This can raise some exceptions, but we'll catch them elsewhere
if not CONF['service:mdns'].all_tcp:
if not use_all_tcp():
return dns.query.udp(
dns_message, ip_address, port=port, timeout=timeout)
return dns.query.tcp(

View File

@ -1,38 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Author: Kiall Mac Innes <kiall@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_log import log as logging
import oslo_messaging as messaging
from designate.central import rpcapi as central_api
LOG = logging.getLogger(__name__)
class BaseEndpoint(object):
# Endpoints which extend this base must provide these properties
RPC_API_NAMESPACE = None
RPC_API_VERSION = None
def __init__(self, tg):
LOG.info("Initialized mDNS %s endpoint", self.RPC_API_NAMESPACE)
self.tg = tg
self.target = messaging.Target(
namespace=self.RPC_API_NAMESPACE,
version=self.RPC_API_VERSION)
@property
def central_api(self):
return central_api.CentralAPI.get_instance()

View File

@ -23,9 +23,9 @@ import dns.rdatatype
from oslo_config import cfg
from oslo_log import log as logging
from designate.central import rpcapi as central_api
from designate import exceptions
from designate.mdns import xfr
from designate.worker import rpcapi as worker_api
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -38,18 +38,18 @@ CONF.import_opt('default_pool_id', 'designate.central',
TSIG_RRSIZE = 10 + 64 + 160 + 1
class RequestHandler(xfr.XFRMixin):
class RequestHandler(object):
def __init__(self, storage, tg):
self._central_api = None
self._worker_api = None
self.storage = storage
self.tg = tg
@property
def central_api(self):
if not self._central_api:
self._central_api = central_api.CentralAPI.get_instance()
return self._central_api
def worker_api(self):
if not self._worker_api:
self._worker_api = worker_api.WorkerAPI.get_instance()
return self._worker_api
def __call__(self, request):
"""
@ -169,8 +169,7 @@ class RequestHandler(xfr.XFRMixin):
'master_addr': master_addr.to_data()
}
)
self.tg.add_thread(self.zone_sync, context, zone,
[master_addr])
self.worker_api.perform_zone_xfr(context, zone, [master_addr])
response.flags |= dns.flags.AA

View File

@ -1,255 +0,0 @@
# Copyright (c) 2014 Rackspace Hosting
# All Rights Reserved.
#
# 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 socket
import time
import dns
import dns.exception
import dns.flags
import dns.message
import dns.opcode
import dns.rcode
import dns.rdataclass
import dns.rdatatype
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
from designate import dnsutils
from designate.mdns import base
dns_query = eventlet.import_patched('dns.query')
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class NotifyEndpoint(base.BaseEndpoint):
RPC_API_VERSION = '2.2'
RPC_API_NAMESPACE = 'notify'
def get_serial_number(self, context, zone, host, port, timeout,
retry_interval, max_retries, delay):
"""
Get zone serial number from a resolver using retries.
:param context: The user context.
:param zone: The designate zone object. This contains the zone
name. zone.serial = expected_serial
:param host: A notify is sent to this host.
:param port: A notify is sent to this port.
:param timeout: The time (in seconds) to wait for a SOA response from
nameserver.
:param retry_interval: The time (in seconds) between retries.
:param max_retries: The maximum number of retries mindns would do for
an expected serial number. After this many retries, mindns returns
an ERROR.
:param delay: The time to wait before sending the first request.
:return: a tuple of (status, actual_serial, retries)
status is either "SUCCESS" or "ERROR".
actual_serial is either the serial number returned in the SOA
message from the nameserver or None.
retries is the number of retries left.
The return value is just used for testing and not by pool manager.
The pool manager is informed of the status with update_status.
"""
actual_serial = None
status = 'ERROR'
retries_left = max_retries
time.sleep(delay)
while True:
response, retry_cnt = self._make_and_send_dns_message(
zone, host, port, timeout, retry_interval, retries_left)
if response and (response.rcode() in (dns.rcode.NXDOMAIN,
dns.rcode.REFUSED,
dns.rcode.SERVFAIL) or
not bool(response.answer)):
status = 'NO_ZONE'
if zone.serial == 0 and zone.action in ('DELETE', 'NONE'):
actual_serial = 0
break # Zone not expected to exist
elif response and len(response.answer) == 1 \
and str(response.answer[0].name) == str(zone.name) \
and response.answer[0].rdclass == dns.rdataclass.IN \
and response.answer[0].rdtype == dns.rdatatype.SOA:
# parse the SOA response and get the serial number
rrset = response.answer[0]
actual_serial = list(rrset.to_rdataset().items)[0].serial
# TODO(vinod): Account for serial number wrap around. Unix
# timestamps are used where Designate is primary, but secondary
# zones use different values.
if actual_serial is not None and actual_serial >= zone.serial:
# Everything looks good at this point. Return SUCCESS.
status = 'SUCCESS'
break
retries_left -= retry_cnt
msg = ("Got lower serial for '%(zone)s' to '%(host)s:"
"%(port)s'. Expected:'%(es)d'. Got:'%(as)s'."
"Retries left='%(retries)d'") % {
'zone': zone.name, 'host': host, 'port': port,
'es': zone.serial, 'as': actual_serial,
'retries': retries_left}
if not retries_left:
# return with error
LOG.warning(msg)
break
LOG.debug(msg)
# retry again
time.sleep(retry_interval)
# Return retries_left for testing purposes.
return status, actual_serial, retries_left
def _make_and_send_dns_message(self, zone, host, port, timeout,
retry_interval, max_retries, notify=False):
"""
Generate and send a DNS message over TCP or UDP using retries
and return response.
:param zone: The designate zone object. This contains the zone
name.
:param host: The destination host for the dns message.
:param port: The destination port for the dns message.
:param timeout: The time (in seconds) to wait for a response from
destination.
:param retry_interval: The time (in seconds) between retries.
:param max_retries: The maximum number of retries mindns would do for
a response. After this many retries, the function returns.
:param notify: If true, a notify message is constructed else a SOA
message is constructed.
:return: a tuple of (response, current_retry) where
response is the response on success or None on failure.
current_retry is the current retry number
"""
dns_message = self._make_dns_message(zone.name, notify=notify)
retry = 0
response = None
while retry < max_retries:
retry += 1
LOG.info("Sending '%(msg)s' for '%(zone)s' to '%(server)s:"
"%(port)d'.",
{'msg': 'NOTIFY' if notify else 'SOA',
'zone': zone.name, 'server': host,
'port': port})
try:
response = dnsutils.send_dns_message(
dns_message, host, port, timeout=timeout
)
except socket.error as e:
if e.errno != socket.errno.EAGAIN:
raise # unknown error, let it traceback
# Initial workaround for bug #1558096
LOG.info("Got EAGAIN while trying to send '%(msg)s' for "
"'%(zone)s' to '%(server)s:%(port)d'. "
"Timeout='%(timeout)d' seconds. Retry='%(retry)d'",
{'msg': 'NOTIFY' if notify else 'SOA',
'zone': zone.name, 'server': host,
'port': port, 'timeout': timeout,
'retry': retry})
# retry sending the message
time.sleep(retry_interval)
continue
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. Retry='%(retry)d'",
{'msg': 'NOTIFY' if notify else 'SOA',
'zone': zone.name, 'server': host,
'port': port, 'timeout': timeout,
'retry': retry})
# retry sending the message if we get a Timeout.
time.sleep(retry_interval)
continue
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. Retry='%(retry)d'",
{'msg': 'NOTIFY' if notify else 'SOA',
'zone': zone.name, 'server': host,
'port': port, 'timeout': timeout,
'retry': retry})
break # no retries after BadResponse
# either we have a good response or an error that we don't want to
# recover by retrying
break
if not response:
return None, retry
# Check that we actually got a NOERROR in the rcode and and an
# authoritative answer
refused_statuses = (
dns.rcode.NXDOMAIN, dns.rcode.REFUSED, dns.rcode.SERVFAIL
)
if (response.rcode() in refused_statuses or
(response.rcode() == dns.rcode.NOERROR and
not bool(response.answer))):
if notify:
LOG.info(
'%(zone)s not found on %(server)s:%(port)d',
{
'zone': zone.name,
'server': host,
'port': port
}
)
elif (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'.\nResponse message:\n%(resp)s\n",
{'msg': 'NOTIFY' if notify else 'SOA',
'zone': zone.name, 'server': host,
'port': port, 'resp': str(response)})
response = None
return response, retry
def _make_dns_message(self, zone_name, notify=False):
"""
This constructs a SOA query or a dns NOTIFY message.
:param zone_name: The zone name for which a SOA/NOTIFY needs to be
sent.
:param notify: If true, a notify message is constructed else a SOA
message is constructed.
:return: The constructed message.
"""
dns_message = dns.message.make_query(zone_name, dns.rdatatype.SOA)
dns_message.flags = 0
if notify:
dns_message.set_opcode(dns.opcode.NOTIFY)
dns_message.flags |= dns.flags.AA
else:
# Setting the flags to RD causes BIND9 to respond with a NXDOMAIN.
dns_message.set_opcode(dns.opcode.QUERY)
dns_message.flags |= dns.flags.RD
return dns_message

View File

@ -1,104 +0,0 @@
# Copyright (c) 2014 Rackspace Hosting
# All Rights Reserved.
#
# 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
from oslo_log import log as logging
import oslo_messaging as messaging
from designate.common import profiler
from designate.loggingutils import rpc_logging
from designate import rpc
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
MDNS_API = None
def reset():
global MDNS_API
MDNS_API = None
@profiler.trace_cls("rpc")
@rpc_logging(LOG, 'mdns')
class MdnsAPI(object):
"""
Client side of the mdns RPC API.
Notify API version history:
1.0 - Added notify_zone_changed and poll_for_serial_number.
1.1 - Added get_serial_number.
2.0 - Changed method signatures.
2.1 - Removed unused functions.
2.2 - Changed get_serial_number signature to make upgrade safer.
XFR API version history:
1.0 - Added perform_zone_xfr.
"""
RPC_NOTIFY_API_VERSION = '2.2'
RPC_XFR_API_VERSION = '1.0'
def __init__(self, topic=None):
self.topic = topic if topic else cfg.CONF['service:mdns'].topic
notify_target = messaging.Target(topic=self.topic,
namespace='notify',
version=self.RPC_NOTIFY_API_VERSION)
self.notify_client = rpc.get_client(notify_target, version_cap='2.2')
xfr_target = messaging.Target(topic=self.topic,
namespace='xfr',
version=self.RPC_XFR_API_VERSION)
self.xfr_client = rpc.get_client(xfr_target, version_cap='1.0')
@classmethod
def get_instance(cls):
"""
The rpc.get_client() which is called upon the API object initialization
will cause a assertion error if the designate.rpc.TRANSPORT isn't setup
by rpc.init() before.
This fixes that by creating the rpcapi when demanded.
"""
global MDNS_API
if not MDNS_API:
MDNS_API = cls()
return MDNS_API
def get_serial_number(self, context, zone, host, port, timeout,
retry_interval, max_retries, delay):
LOG.info(
"get_serial_number: Calling mdns for zone '%(zone)s', serial "
"%(serial)s' on nameserver '%(host)s:%(port)s'",
{
'zone': zone.name,
'serial': zone.serial,
'host': host,
'port': port
})
cctxt = self.notify_client.prepare()
return cctxt.call(
context, 'get_serial_number', zone=zone,
host=host, port=port, timeout=timeout,
retry_interval=retry_interval, max_retries=max_retries,
delay=delay
)
def perform_zone_xfr(self, context, zone):
LOG.info("perform_zone_xfr: Calling mdns for zone %(zone)s",
{"zone": zone.name})
return self.xfr_client.cast(context, 'perform_zone_xfr', zone=zone)

View File

@ -19,8 +19,6 @@ from oslo_log import log as logging
from designate.conf.mdns import DEFAULT_MDNS_PORT
from designate import dnsutils
from designate.mdns import handler
from designate.mdns import notify
from designate.mdns import xfr
from designate import service
from designate import storage
from designate import utils
@ -29,20 +27,15 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class Service(service.RPCService):
class Service(service.Service):
_dns_default_port = DEFAULT_MDNS_PORT
def __init__(self):
self._storage = None
super(Service, self).__init__(
self.service_name, cfg.CONF['service:mdns'].topic,
threads=cfg.CONF['service:mdns'].threads,
self.service_name, threads=cfg.CONF['service:mdns'].threads,
)
self.override_endpoints(
[notify.NotifyEndpoint(self.tg), xfr.XfrEndpoint(self.tg)]
)
self.dns_service = service.DNSService(
self.dns_application, self.tg,
cfg.CONF['service:mdns'].listen,

View File

@ -1,64 +0,0 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# Author: Endre Karlson <endre.karlson@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 time
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
from designate import dnsutils
from designate import exceptions
from designate.mdns import base
from designate.metrics import metrics
LOG = logging.getLogger(__name__)
class XFRMixin(object):
"""
Utility mixin that holds common methods for XFR functionality.
"""
def zone_sync(self, context, zone, servers=None):
start_time = time.time()
try:
servers = servers or zone.masters
servers = servers.to_list()
timeout = cfg.CONF["service:mdns"].xfr_timeout
try:
dnspython_zone = dnsutils.do_axfr(zone.name, servers,
timeout=timeout)
except exceptions.XFRFailure as e:
LOG.warning(e)
return
zone.update(dnsutils.from_dnspython_zone(dnspython_zone))
zone.transferred_at = timeutils.utcnow()
zone.obj_reset_changes(["name"])
self.central_api.update_zone(context, zone, increment_serial=False)
finally:
metrics.timing('mdns.xfr.zone_sync', time.time() - start_time)
class XfrEndpoint(base.BaseEndpoint, XFRMixin):
RPC_API_VERSION = '1.0'
RPC_API_NAMESPACE = 'xfr'
def perform_zone_xfr(self, context, zone):
self.zone_sync(context, zone)

View File

@ -1,80 +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.
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
import designate.conf
from designate.metrics_client import noop
monascastatsd = importutils.try_import('monascastatsd')
CFG_GROUP_NAME = 'monasca:statsd'
CONF = designate.conf.CONF
LOG = logging.getLogger(__name__)
# Global metrics client to be imported by other modules
metrics = None
class Metrics(object):
def __init__(self):
self._client = None
def init(self):
conf = cfg.CONF[CFG_GROUP_NAME]
if conf.enabled and monascastatsd:
LOG.info(
'Statsd reports to %(host)s:%(port)d',
{
'host': conf.hostname,
'port': conf.port
}
)
self._client = monascastatsd.Client(
host=conf.hostname, port=conf.port,
dimensions={
'service_name': 'dns'
})
return
if conf.enabled and not monascastatsd:
LOG.error('monasca-statsd client not installed. '
'Metrics will be ignored.')
else:
LOG.info('Statsd disabled')
self._client = noop.Client()
def counter(self, *a, **kw):
return self.client.get_counter(*a, **kw)
def gauge(self, *a, **kw):
return self.client.get_gauge(*a, **kw)
@property
def timing(self):
return self.client.get_timer().timing
def timer(self):
return self.client.get_timer()
@property
def client(self):
if not self._client:
self.init()
return self._client
metrics = Metrics()

View File

@ -1,85 +0,0 @@
#
# Copyright (C) 2016 Red Hat, Inc.
#
# 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
LOG = logging.getLogger(__name__)
class NoopConnection(object):
def __init__(self):
pass
def _flush_buffer(self):
pass
def close_buffer(self):
pass
def connect(self, *a, **kw):
pass
def open_buffer(self, *a, **kw):
pass
class NoopCounter(object):
def __init__(self):
pass
def increment(self, *a, **kw):
pass
def decrement(self, *a, **kw):
pass
def __add__(self, value):
pass
def __sub__(self, value):
pass
class NoopGauge(object):
def __init__(self):
pass
def send(self, *a, **kw):
pass
class NoopTimer(object):
def __init__(self):
pass
def timing(self, *a, **kw):
pass
class Client(object):
def __init__(self, *a, **kw):
self._counter = NoopCounter()
self._gauge = NoopGauge()
self._timer = NoopTimer()
self.connection = NoopConnection()
def get_counter(self, *a, **kw):
return self._counter
def get_gauge(self, *a, **kw):
return self._gauge
def get_timer(self):
return self._timer

View File

@ -33,7 +33,6 @@ from oslo_utils import netutils
from designate.common import profiler
import designate.conf
from designate.i18n import _
from designate.metrics import metrics
from designate import policy
from designate import rpc
from designate import utils
@ -150,7 +149,6 @@ class DNSService(object):
self.tcp_backlog = tcp_backlog
self.tcp_recv_timeout = tcp_recv_timeout
self.listen = listen
metrics.init()
# Eventet will complain loudly about our use of multiple greentheads
# reading/writing to the UDP socket at once. Disable this warning.

View File

@ -21,9 +21,9 @@ import oslo_messaging as messaging
from designate.central import service as central_service
from designate import exceptions
from designate.mdns import rpcapi as mdns_api
from designate import objects
from designate.tests.test_api.test_v2 import ApiV2TestCase
from designate.worker import rpcapi as worker_api
class ApiV2ZonesTest(ApiV2TestCase):
@ -565,16 +565,17 @@ class ApiV2ZonesTest(ApiV2TestCase):
# Create a zone
zone = self.create_zone(**fixture)
mdns = mock.Mock()
with mock.patch.object(mdns_api.MdnsAPI, 'get_instance') as get_mdns:
get_mdns.return_value = mdns
mdns.get_serial_number.return_value = ('SUCCESS', 10, 1, )
worker = mock.Mock()
with mock.patch.object(worker_api.WorkerAPI,
'get_instance') as get_worker:
get_worker.return_value = worker
worker.get_serial_number.return_value = ('SUCCESS', 10)
response = self.client.post_json(
'/zones/%s/tasks/xfr' % zone['id'],
None, status=202)
self.assertTrue(mdns.perform_zone_xfr.called)
self.assertTrue(worker.perform_zone_xfr.called)
# Check the headers are what we expect
self.assertEqual(202, response.status_int)

View File

@ -34,12 +34,12 @@ import testtools
from testtools.matchers import GreaterThan
from designate import exceptions
from designate.mdns import rpcapi as mdns_api
from designate import objects
from designate.storage.impl_sqlalchemy import tables
from designate.tests import fixtures
from designate.tests.test_central import CentralTestCase
from designate import utils
from designate.worker import rpcapi as worker_api
LOG = logging.getLogger(__name__)
@ -1307,13 +1307,15 @@ class CentralServiceTest(CentralTestCase):
# Create a zone
secondary = self.create_zone(**fixture)
mdns = mock.Mock()
with mock.patch.object(mdns_api.MdnsAPI, 'get_instance') as get_mdns:
get_mdns.return_value = mdns
mdns.get_serial_number.return_value = ('SUCCESS', 10, 1, )
worker = mock.Mock()
with mock.patch.object(worker_api.WorkerAPI,
'get_instance') as get_worker:
get_worker.return_value = worker
worker.get_serial_number.return_value = ('SUCCESS', 10)
self.central_service.xfr_zone(self.admin_context, secondary.id)
self.assertTrue(mdns.perform_zone_xfr.called)
self.assertTrue(worker.perform_zone_xfr.called)
def test_xfr_zone_same_serial(self):
# Create a zone
@ -1324,13 +1326,14 @@ class CentralServiceTest(CentralTestCase):
# Create a zone
secondary = self.create_zone(**fixture)
mdns = mock.Mock()
with mock.patch.object(mdns_api.MdnsAPI, 'get_instance') as get_mdns:
get_mdns.return_value = mdns
mdns.get_serial_number.return_value = ('SUCCESS', 1, 1, )
worker = mock.Mock()
with mock.patch.object(worker_api.WorkerAPI,
'get_instance') as get_worker:
get_worker.return_value = worker
worker.get_serial_number.return_value = ('SUCCESS', 1)
self.central_service.xfr_zone(self.admin_context, secondary.id)
self.assertFalse(mdns.perform_zone_xfr.called)
self.assertFalse(worker.perform_zone_xfr.called)
def test_xfr_zone_lower_serial(self):
# Create a zone
@ -1343,13 +1346,14 @@ class CentralServiceTest(CentralTestCase):
secondary = self.create_zone(**fixture)
secondary.serial
mdns = mock.Mock()
with mock.patch.object(mdns_api.MdnsAPI, 'get_instance') as get_mdns:
get_mdns.return_value = mdns
mdns.get_serial_number.return_value = ('SUCCESS', 0, 1, )
worker = mock.Mock()
with mock.patch.object(worker_api.WorkerAPI,
'get_instance') as get_worker:
get_worker.return_value = worker
worker.get_serial_number.return_value = ('SUCCESS', 0)
self.central_service.xfr_zone(self.admin_context, secondary.id)
self.assertFalse(mdns.perform_zone_xfr.called)
self.assertFalse(worker.perform_zone_xfr.called)
def test_xfr_zone_invalid_type(self):
zone = self.create_zone()

View File

@ -177,9 +177,6 @@ class MdnsRequestHandlerTest(MdnsTestCase):
return_value=zone):
response = next(self.handler(request)).to_wire()
self.mock_tg.add_thread.assert_called_with(
self.handler.zone_sync, self.context, zone,
[zone.masters[0]])
self.assertEqual(expected_response, binascii.b2a_hex(response))
@mock.patch.object(dns.resolver.Resolver, 'query')

View File

@ -21,13 +21,12 @@ import dns.exception
import dns.message
import dns.query
from designate.mdns import notify
from designate import objects
from designate.tests.test_mdns import MdnsTestCase
from designate.tests import TestCase
from designate.worker.tasks import zone
class MdnsNotifyTest(MdnsTestCase):
class WorkerNotifyTest(TestCase):
test_zone = {
'name': 'example.com.',
'email': 'example@example.com',
@ -35,14 +34,13 @@ class MdnsNotifyTest(MdnsTestCase):
}
def setUp(self):
super(MdnsNotifyTest, self).setUp()
super(WorkerNotifyTest, self).setUp()
self.nameserver = objects.PoolNameserver.from_dict({
'id': 'f278782a-07dc-4502-9177-b5d85c5f7c7e',
'host': '127.0.0.1',
'port': 65255
})
self.mock_tg = mock.Mock()
self.notify = notify.NotifyEndpoint(self.mock_tg)
def test_poll_for_serial_number(self):
# id 10001
@ -62,12 +60,14 @@ class MdnsNotifyTest(MdnsTestCase):
"00000e10")
with patch.object(dns.query, 'udp', return_value=dns.message.from_wire(
binascii.a2b_hex(poll_response))):
status, serial, retries = self.notify.get_serial_number(
'context', objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port, 0, 0, 2, 0)
self.assertEqual(status, 'SUCCESS')
self.assertEqual(serial, self.test_zone['serial'])
self.assertEqual(retries, 2)
get_zone_serial = zone.GetZoneSerial(
self.mock_tg, 'context',
objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port,
)
result = get_zone_serial()
self.assertEqual(result[0], 'SUCCESS')
self.assertEqual(result[1], self.test_zone['serial'])
def test_poll_for_serial_number_lower_serial(self):
# id 10001
@ -87,12 +87,14 @@ class MdnsNotifyTest(MdnsTestCase):
"00000e10")
with patch.object(dns.query, 'udp', return_value=dns.message.from_wire(
binascii.a2b_hex(poll_response))):
status, serial, retries = self.notify.get_serial_number(
'context', objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port, 0, 0, 2, 0)
self.assertEqual(status, 'ERROR')
self.assertEqual(serial, 99)
self.assertEqual(retries, 0)
get_zone_serial = zone.GetZoneSerial(
self.mock_tg, 'context',
objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port,
)
result = get_zone_serial()
self.assertEqual(result[0], 'SUCCESS')
self.assertEqual(result[1], 99)
def test_poll_for_serial_number_higher_serial(self):
# id 10001
@ -112,18 +114,23 @@ class MdnsNotifyTest(MdnsTestCase):
"00000e10")
with patch.object(dns.query, 'udp', return_value=dns.message.from_wire(
binascii.a2b_hex(poll_response))):
status, serial, retries = self.notify.get_serial_number(
'context', objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port, 0, 0, 2, 0)
self.assertEqual(status, 'SUCCESS')
self.assertEqual(serial, 101)
self.assertEqual(retries, 2)
get_zone_serial = zone.GetZoneSerial(
self.mock_tg, 'context',
objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port,
)
result = get_zone_serial()
self.assertEqual(result[0], 'SUCCESS')
self.assertEqual(result[1], 101)
@patch.object(dns.query, 'udp', side_effect=dns.exception.Timeout)
def test_poll_for_serial_number_timeout(self, _):
status, serial, retries = self.notify.get_serial_number(
'context', objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port, 0, 0, 2, 0)
self.assertEqual(status, 'ERROR')
self.assertIsNone(serial)
self.assertEqual(retries, 0)
self.CONF.set_override('serial_timeout', 1, 'service:worker')
get_zone_serial = zone.GetZoneSerial(
self.mock_tg, 'context',
objects.Zone.from_dict(self.test_zone),
self.nameserver.host, self.nameserver.port,
)
result = get_zone_serial()
self.assertEqual(result[0], 'ERROR')
self.assertIsNone(result[1])

View File

@ -24,6 +24,7 @@ from designate import exceptions
from designate.mdns import handler
from designate import objects
from designate.tests import fixtures
from designate.worker import rpcapi as worker_rpcapi
CONF = cfg.CONF
@ -39,6 +40,14 @@ class MdnsHandleTest(oslotest.base.BaseTestCase):
self.tg = mock.Mock()
self.handler = handler.RequestHandler(self.storage, self.tg)
def test_worker_api(self):
self.assertIsNone(self.handler._worker_api)
self.assertIsInstance(self.handler.worker_api,
worker_rpcapi.WorkerAPI)
self.assertIsNotNone(self.handler._worker_api)
self.assertIsInstance(self.handler.worker_api,
worker_rpcapi.WorkerAPI)
@mock.patch.object(dns.resolver.Resolver, 'query')
def test_notify(self, mock_query):
self.storage.find_zone.return_value = objects.Zone(
@ -206,7 +215,6 @@ class TestRequestHandlerCall(oslotest.base.BaseTestCase):
def setUp(self):
super(TestRequestHandlerCall, self).setUp()
self.handler = handler.RequestHandler(mock.Mock(), mock.Mock())
self.handler._central_api = mock.Mock(name='central_api')
# Use a simple handlers that doesn't require a real request
self.handler._handle_query_error = mock.Mock(return_value='Error')
@ -215,10 +223,6 @@ class TestRequestHandlerCall(oslotest.base.BaseTestCase):
return_value=['Record Query'])
self.handler._handle_notify = mock.Mock(return_value=['Notify'])
def test_central_api_property(self):
self.handler._central_api = 'foo'
self.assertEqual(self.handler.central_api, 'foo')
def test__call___unhandled_opcodes(self):
unhandled_codes = [
dns.opcode.STATUS,

View File

@ -1,258 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 socket
from unittest import mock
import dns
import dns.rdataclass
import dns.rdatatype
from designate import dnsutils
import designate.mdns.notify as notify
import designate.tests
from designate.tests.unit import RoObject
class MdnsNotifyTest(designate.tests.TestCase):
def setUp(self):
super(MdnsNotifyTest, self).setUp()
self.notify = notify.NotifyEndpoint(mock.Mock())
@mock.patch('time.sleep')
def test_get_serial_number_nxdomain(self, mock_sleep):
# The zone is not found but it was supposed to be there
response = RoObject(
answer=[RoObject(
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA
)],
rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN)
)
zone = RoObject(name='zn', serial=314)
self.notify._make_and_send_dns_message = mock.Mock(
return_value=(response, 1)
)
out = self.notify.get_serial_number(
'context', zone, 'h', 1234, 1, 2, 3, 4
)
self.assertEqual(('NO_ZONE', None, 0), out)
@mock.patch('time.sleep')
def test_get_serial_number_nxdomain_deleted_zone(self, mock_sleep):
# The zone is not found and it's not was supposed be there
response = RoObject(
answer=[RoObject(
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA
)],
rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN)
)
zone = RoObject(name='zn', serial=0, action='DELETE')
self.notify._make_and_send_dns_message = mock.Mock(
return_value=(response, 1)
)
out = self.notify.get_serial_number(
'context', zone, 'h', 1234, 1, 2, 3, 4
)
self.assertEqual(('NO_ZONE', 0, 3), out)
@mock.patch('time.sleep')
def test_get_serial_number_ok(self, mock_sleep):
zone = RoObject(name='zn', serial=314)
ds = RoObject(items=[zone])
response = RoObject(
answer=[RoObject(
name='zn',
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA,
to_rdataset=mock.Mock(return_value=ds)
)],
rcode=mock.Mock(return_value=dns.rcode.NOERROR)
)
self.notify._make_and_send_dns_message = mock.Mock(
return_value=(response, 1)
)
out = self.notify.get_serial_number(
'context', zone, 'h', 1234, 1, 2, 3, 4
)
self.assertEqual(('SUCCESS', 314, 3), out)
@mock.patch('time.sleep')
def test_get_serial_number_too_many_retries(self, mock_sleep):
zone = RoObject(name='zn', serial=314)
ds = RoObject(items=[RoObject(serial=310)])
response = RoObject(
answer=[RoObject(
name='zn',
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA,
to_rdataset=mock.Mock(return_value=ds)
)],
rcode=mock.Mock(return_value=dns.rcode.NOERROR)
)
self.notify._make_and_send_dns_message = mock.Mock(
return_value=(response, 1)
)
out = self.notify.get_serial_number(
'context', zone, 'h', 1234, 1, 2, 3, 4
)
self.assertEqual(('ERROR', 310, 0), out)
@mock.patch('time.sleep')
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_timeout(self, mock_send_dns_message,
mock_sleep):
zone = RoObject(name='zn')
mock_send_dns_message.side_effect = dns.exception.Timeout
out = self.notify._make_and_send_dns_message(
zone, 'host', 123, 1, 2, 3
)
self.assertEqual((None, 3), out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_bad_response(self,
mock_send_dns_message):
zone = RoObject(name='zn')
self.notify._make_dns_message = mock.Mock(return_value='')
mock_send_dns_message.side_effect = notify.dns_query.BadResponse
out = self.notify._make_and_send_dns_message(
zone, 'host', 123, 1, 2, 3
)
self.assertEqual((None, 1), out)
@mock.patch('time.sleep')
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_eagain(self, mock_send_dns_message,
mock_sleep):
# bug #1558096
zone = RoObject(name='zn')
socket_error = socket.error()
socket_error.errno = socket.errno.EAGAIN
mock_send_dns_message.side_effect = socket_error
out = self.notify._make_and_send_dns_message(
zone, 'host', 123, 1, 2, 3
)
self.assertEqual((None, 3), out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_econnrefused(self,
mock_send_dns_message):
# bug #1558096
zone = RoObject(name='zn')
socket_error = socket.error()
socket_error.errno = socket.errno.ECONNREFUSED
# socket errors other than EAGAIN should raise
mock_send_dns_message.side_effect = socket_error
self.assertRaises(
socket.error,
self.notify._make_and_send_dns_message,
zone, 'host', 123, 1, 2, 3
)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_nxdomain(self, mock_send_dns_message):
zone = RoObject(name='zn')
response = RoObject(rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN))
mock_send_dns_message.return_value = response
out = self.notify._make_and_send_dns_message(
zone, 'host', 123, 1, 2, 3
)
self.assertEqual((response, 1), out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_missing_AA_flags(self,
mock_send_dns_message):
zone = RoObject(name='zn')
response = RoObject(
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
# rcode is NOERROR but (flags & dns.flags.AA) gives 0
flags=0,
answer=['answer'],
)
mock_send_dns_message.return_value = response
out = self.notify._make_and_send_dns_message(
zone, 'host', 123, 1, 2, 3
)
self.assertEqual((None, 1), out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_error_flags(self,
mock_send_dns_message):
zone = RoObject(name='zn')
response = RoObject(
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
# rcode is NOERROR but flags are not NOERROR
flags=123,
ednsflags=321,
answer=['answer'],
)
mock_send_dns_message.return_value = response
out = self.notify._make_and_send_dns_message(
zone, 'host', 123, 1, 2, 3
)
self.assertEqual((None, 1), out)
def test_make_dns_message(self):
msg = self.notify._make_dns_message('zone_name')
txt = msg.to_text().split('\n')[1:]
self.assertEqual([
'opcode QUERY',
'rcode NOERROR',
'flags RD',
';QUESTION',
'zone_name. IN SOA',
';ANSWER',
';AUTHORITY',
';ADDITIONAL'
], txt)
def test_make_dns_message_notify(self):
msg = self.notify._make_dns_message('zone_name', notify=True)
txt = msg.to_text().split('\n')[1:]
self.assertEqual([
'opcode NOTIFY',
'rcode NOERROR',
'flags AA',
';QUESTION',
'zone_name. IN SOA',
';ANSWER',
';AUTHORITY',
';ADDITIONAL',
], txt)

View File

@ -44,12 +44,10 @@ class MdnsServiceTest(oslotest.base.BaseTestCase):
self.service = service.Service()
@mock.patch.object(designate.service.DNSService, 'start')
@mock.patch.object(designate.service.RPCService, 'start')
def test_service_start(self, mock_rpc_start, mock_dns_start):
def test_service_start(self, mock_dns_start):
self.service.start()
self.assertTrue(mock_dns_start.called)
self.assertTrue(mock_rpc_start.called)
def test_service_stop(self):
self.service.dns_service.stop = mock.Mock()
@ -63,14 +61,6 @@ class MdnsServiceTest(oslotest.base.BaseTestCase):
def test_service_name(self):
self.assertEqual('mdns', self.service.service_name)
def test_mdns_rpc_topic(self):
CONF.set_override('topic', 'test-topic', 'service:mdns')
self.service = service.Service()
self.assertEqual('test-topic', self.service.rpc_topic)
self.assertEqual('mdns', self.service.service_name)
@mock.patch.object(storage, 'get_storage')
def test_storage_driver(self, mock_get_driver):
self.service._storage = None

View File

@ -1,57 +0,0 @@
# Copyright 2019 Inspur
#
# Author: ZhouHeng <zhouhenglc@inspur.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 oslo_config import fixture as cfg_fixture
import oslotest.base
from designate import dnsutils
from designate.mdns import xfr
from designate import objects
from designate.tests import fixtures
CONF = cfg.CONF
class MdnsXFRMixinTest(oslotest.base.BaseTestCase):
def setUp(self):
super(MdnsXFRMixinTest, self).setUp()
self.stdlog = fixtures.StandardLogging()
self.useFixture(self.stdlog)
self.useFixture(cfg_fixture.Config(CONF))
self.context = mock.Mock()
self.tg = mock.Mock()
self.xfrMixin = xfr.XFRMixin()
self.xfrMixin.central_api = mock.Mock()
def test_zone_sync_not_change_name(self):
zone = objects.Zone(id='7592878e-4ade-40de-8b8d-699b871ee6fa',
name="example.com.",
serial=1,
masters=objects.ZoneMasterList.from_list([
{'host': '127.0.0.1', 'port': 53}, ]))
with mock.patch.object(dnsutils, 'do_axfr') as mock_axfr, \
mock.patch.object(dnsutils, 'from_dnspython_zone') as mock2:
mock_axfr.return_value = mock.Mock()
mock2.return_value = zone
self.xfrMixin.zone_sync(self.context, zone)
self.assertIn("transferred_at", zone.obj_what_changed())
self.assertNotIn("name", zone.obj_what_changed())

View File

@ -1,125 +0,0 @@
#
# Copyright (C) 2016 Red Hat, Inc.
#
# 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 time
from unittest import mock
import monascastatsd
from oslo_config import cfg
from oslo_config import fixture as cfg_fixture
from designate import metrics
from designate.metrics_client import noop
from designate.tests import fixtures
from designate.tests import TestCase
class TestNoopMetrics(TestCase):
def setUp(self):
super(TestCase, self).setUp()
self.stdlog = fixtures.StandardLogging()
self.useFixture(self.stdlog)
self.CONF = self.useFixture(cfg_fixture.Config(cfg.CONF)).conf
self.CONF.set_override('enabled', False, 'monasca:statsd')
def test_monasca_metrics_disabled(self):
self.metrics = metrics.Metrics()
self.assertIsInstance(self.metrics.client, noop.Client)
self.assertIn('Statsd disabled', self.stdlog.logger.output)
def test_noop_metrics_client_getters(self):
self.metrics = metrics.Metrics()
self.assertIsInstance(self.metrics.counter('name'), noop.NoopCounter)
self.assertIsInstance(self.metrics.gauge(), noop.NoopGauge)
self.assertIsInstance(self.metrics.timer(), noop.NoopTimer)
self.assertIsNotNone(self.metrics.timer.__self__)
def test_noop_metrics_client_timed(self):
self.metrics = metrics.Metrics()
timer = self.metrics.client.get_timer()
def func(a):
start_time = time.time()
try:
return a
finally:
timer.timing('mdns.xfr.zone_sync', time.time() - start_time)
result = func(1)
self.assertEqual(result, 1)
class TestMonascaMetrics(TestCase):
def setUp(self):
super(TestCase, self).setUp()
self.stdlog = fixtures.StandardLogging()
self.useFixture(self.stdlog)
self.CONF = self.useFixture(cfg_fixture.Config(cfg.CONF)).conf
self.CONF.set_override('enabled', True, 'monasca:statsd')
@mock.patch('socket.socket.connect')
def test_monasca_metrics_enabled(self, conn_mock):
self.metrics = metrics.Metrics()
self.assertIsInstance(self.metrics.client, monascastatsd.client.Client)
self.assertIn('Statsd reports to 127.0.0.1:8125',
self.stdlog.logger.output)
self.assertTrue(conn_mock.called)
@mock.patch('socket.socket.connect')
def test_monasca_metrics_client_getters(self, conn_mock):
self.metrics = metrics.Metrics()
self.assertIsInstance(self.metrics.counter('name'),
monascastatsd.counter.Counter)
self.assertIsInstance(self.metrics.gauge(),
monascastatsd.gauge.Gauge)
self.assertIsInstance(self.metrics.timer(),
monascastatsd.timer.Timer)
self.assertIsNotNone(self.metrics.timer.__self__)
self.assertTrue(conn_mock.called)
@mock.patch('socket.socket.send')
@mock.patch('socket.socket.connect')
def test_monasca_metrics_client_timed(self, conn_mock, send_mock):
self.metrics = metrics.Metrics()
timer = self.metrics.client.get_timer()
def func(a):
start_time = time.time()
try:
return a
finally:
timer.timing('mdns.xfr.zone_sync', time.time() - start_time)
result = func(1)
self.assertEqual(result, 1)
self.assertTrue(conn_mock.called)
self.assertTrue(send_mock.called)
def test_monasca_enabled_but_client_not_installed(self):
restore = metrics.monascastatsd
try:
metrics.monascastatsd = None
self.metrics = metrics.Metrics()
self.assertIsInstance(self.metrics.client, noop.Client)
self.assertIn(
'monasca-statsd client not installed. '
'Metrics will be ignored.',
self.stdlog.logger.output
)
finally:
metrics.monascastatsd = restore

View File

@ -198,15 +198,6 @@ class MockPool(object):
# Fixtures
fx_mdns_api = fixtures.MockPatch('designate.central.service.mdns_rpcapi')
mdns_api = mock.PropertyMock(
return_value=mock.NonCallableMagicMock(spec_set=[
'a'
])
)
fx_worker = fixtures.MockPatch(
'designate.central.service.worker_rpcapi.WorkerAPI.get_instance',
mock.MagicMock(spec_set=[
@ -281,12 +272,6 @@ class CentralBasic(TestCase):
class CentralServiceTestCase(CentralBasic):
def test_mdns_api_patch(self):
with fx_mdns_api:
q = self.service.mdns_api
assert 'mdns_rpcapi.MdnsAPI.get_instance' in repr(q)
def test_conf_fixture(self):
assert 'service:central' in designate.central.service.cfg.CONF
@ -1017,13 +1002,14 @@ class CentralZoneTestCase(CentralBasic):
masters=[RoObject(host='10.0.0.1', port=53)],
serial=1,
)
with fx_mdns_api:
self.service.mdns_api.get_serial_number.return_value = \
"SUCCESS", 2, 1
with fx_worker:
self.service.worker_api.get_serial_number.return_value = (
'SUCCESS', 2
)
self.service.xfr_zone(
self.context, CentralZoneTestCase.zone__id)
self.assertTrue(
self.service.mdns_api.perform_zone_xfr.called)
self.service.worker_api.perform_zone_xfr.called)
self.assertTrue(designate.central.service.policy.check.called)
self.assertEqual(

View File

@ -24,6 +24,7 @@ import dns.rdatatype
import dns.zone
import eventlet
from oslo_config import cfg
from oslo_config import fixture as cfg_fixture
import oslotest.base
from designate import dnsutils
@ -212,10 +213,52 @@ class TestUtils(designate.tests.TestCase):
# This needs to be a one item tuple for the serialization middleware
self.assertEqual(middleware.process_request(notify), (response,))
def test_all_tcp_default(self):
self.assertEqual(False, dnsutils.use_all_tcp())
def test_all_tcp_using_mdns(self):
CONF.set_override('all_tcp', True, 'service:mdns')
self.assertEqual(True, dnsutils.use_all_tcp())
def test_all_tcp_using_worker(self):
CONF.set_override('all_tcp', True, 'service:worker')
self.assertEqual(True, dnsutils.use_all_tcp())
@mock.patch.object(dns.query, 'udp')
def test_send_soa_message(self, mock_udp):
dnsutils.soa('zone_name', '192.0.2.1', 1234, 1)
msg = mock_udp.call_args[0][0]
mock_udp.assert_called_with(
mock.ANY, '192.0.2.1', port=1234, timeout=1
)
txt = msg.to_text().split('\n')[1:]
self.assertEqual([
'opcode QUERY',
'rcode NOERROR',
'flags RD',
';QUESTION',
'zone_name. IN SOA',
';ANSWER',
';AUTHORITY',
';ADDITIONAL'
], txt)
class TestDoAfxr(oslotest.base.BaseTestCase):
def setUp(self):
super(TestDoAfxr, self).setUp()
self.useFixture(cfg_fixture.Config(CONF))
def test_xfr_default(self):
self.assertEqual(10, dnsutils.xfr_timeout())
def test_xfr_timeout_set_using_mdns(self):
CONF.set_override('xfr_timeout', 30, 'service:mdns')
self.assertEqual(30, dnsutils.xfr_timeout())
def test_xfr_timeout_set_using_worker(self):
CONF.set_override('xfr_timeout', 40, 'service:worker')
self.assertEqual(40, dnsutils.xfr_timeout())
@mock.patch.object(dns.query, 'xfr')
@mock.patch.object(dns.zone, 'from_xfr')

View File

@ -0,0 +1,206 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# 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 socket
from unittest import mock
import dns
import dns.rdataclass
import dns.rdatatype
from oslo_config import cfg
from oslo_config import fixture as cfg_fixture
import oslotest.base
from designate import dnsutils
from designate.tests.unit import RoObject
from designate.worker.tasks import zone as worker_zone
CONF = cfg.CONF
class WorkerNotifyTest(oslotest.base.BaseTestCase):
def setUp(self):
super(WorkerNotifyTest, self).setUp()
self.useFixture(cfg_fixture.Config(CONF))
self.zone = RoObject(name='zn', serial=314)
self.notify = worker_zone.GetZoneSerial(
mock.Mock(), mock.Mock(), self.zone, 'localhost', 1234
)
@mock.patch('time.sleep', mock.Mock())
def test_get_serial_number_nxdomain(self):
CONF.set_override('serial_timeout', 0.1, 'service:worker')
# The zone is not found but it was supposed to be there
response = RoObject(
answer=[RoObject(
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA
)],
rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN)
)
zone = RoObject(name='zn', serial=314)
notify = worker_zone.GetZoneSerial(mock.Mock(), mock.Mock(),
zone, 'localhost',
1234)
notify._make_and_send_soa_message = mock.Mock(
return_value=response
)
self.assertEqual(('NO_ZONE', None), notify())
@mock.patch('time.sleep', mock.Mock())
def test_get_serial_number_nxdomain_deleted_zone(self):
# The zone is not found and it's not was supposed be there
response = RoObject(
answer=[RoObject(
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA
)],
rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN)
)
zone = RoObject(name='zn', serial=0, action='DELETE')
notify = worker_zone.GetZoneSerial(mock.Mock(), mock.Mock(),
zone, 'localhost',
1234)
notify._make_and_send_soa_message = mock.Mock(
return_value=response
)
self.assertEqual(('NO_ZONE', 0), notify())
@mock.patch('time.sleep', mock.Mock())
def test_get_serial_number_ok(self):
zone = RoObject(name='zn', serial=314)
ds = RoObject(items=[zone])
response = RoObject(
answer=[RoObject(
name='zn',
rdclass=dns.rdataclass.IN,
rdtype=dns.rdatatype.SOA,
to_rdataset=mock.Mock(return_value=ds)
)],
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
flags=dns.flags.AA,
ednsflags=dns.rcode.NOERROR,
)
notify = worker_zone.GetZoneSerial(mock.Mock(), mock.Mock(),
zone, 'localhost',
1234)
notify._make_and_send_soa_message = mock.Mock(
return_value=response
)
self.assertEqual(('SUCCESS', 314), notify())
@mock.patch('time.sleep', mock.Mock())
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_error_flags(self,
mock_send_dns_message):
response = RoObject(
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
# rcode is NOERROR but flags are not NOERROR
flags=123,
ednsflags=321,
answer=['answer'],
)
mock_send_dns_message.return_value = response
notify = worker_zone.GetZoneSerial(mock.Mock(), mock.Mock(),
self.zone, 'localhost',
1234)
self.assertEqual(('ERROR', None), notify())
@mock.patch('time.sleep', mock.Mock())
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_missing_AA_flags(self,
mock_send_dns_message):
response = RoObject(
rcode=mock.Mock(return_value=dns.rcode.NOERROR),
# rcode is NOERROR but (flags & dns.flags.AA) gives 0
flags=0,
answer=['answer'],
)
mock_send_dns_message.return_value = response
notify = worker_zone.GetZoneSerial(mock.Mock(), mock.Mock(),
self.zone, 'localhost',
1234)
self.assertEqual(('ERROR', None), notify())
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_timeout(self, mock_send_dns_message):
mock_send_dns_message.side_effect = dns.exception.Timeout
out = self.notify._make_and_send_soa_message(
self.zone.name, 'host', 123
)
self.assertIsNone(out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_bad_response(self,
mock_send_dns_message):
self.notify._make_dns_message = mock.Mock(return_value='')
mock_send_dns_message.side_effect = dns.query.BadResponse
out = self.notify._make_and_send_soa_message(
self.zone.name, 'host', 123
)
self.assertIsNone(out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_eagain(self, mock_send_dns_message):
# bug #1558096
socket_error = socket.error()
socket_error.errno = socket.errno.EAGAIN
mock_send_dns_message.side_effect = socket_error
out = self.notify._make_and_send_soa_message(
self.zone.name, 'host', 123
)
self.assertIsNone(out)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_econnrefused(self,
mock_send_dns_message):
# bug #1558096
socket_error = socket.error()
socket_error.errno = socket.errno.ECONNREFUSED
# socket errors other than EAGAIN should raise
mock_send_dns_message.side_effect = socket_error
self.assertRaises(
socket.error,
self.notify._make_and_send_soa_message,
self.zone.name, 'host', 123
)
@mock.patch.object(dnsutils, 'send_dns_message')
def test_make_and_send_dns_message_nxdomain(self, mock_send_dns_message):
response = RoObject(
rcode=mock.Mock(return_value=dns.rcode.NXDOMAIN),
flags=dns.flags.AA,
ednsflags=dns.rcode.NXDOMAIN
)
mock_send_dns_message.return_value = response
out = self.notify._make_and_send_soa_message(
self.zone.name, 'host', 123
)
self.assertEqual(response, out)

View File

@ -286,3 +286,36 @@ class TestService(oslotest.base.BaseTestCase):
)
self.service.executor.run.assert_called_with(mock_export_zone())
@mock.patch.object(service.zonetasks, 'ZoneXfr')
def test_perform_zone_xfr(self, mock_perform_zone_xfr):
self.service._executor = mock.Mock()
self.service._pool = mock.Mock()
zone = mock.Mock()
self.service.perform_zone_xfr(self.context, zone)
mock_perform_zone_xfr.assert_called_with(
self.service.executor,
self.context,
zone,
None
)
self.service.executor.run.assert_called_with(mock_perform_zone_xfr())
@mock.patch.object(service.zonetasks, 'GetZoneSerial')
def test_get_serial_number(self, mock_get_serial_number):
zone = mock.Mock()
self.service.get_serial_number(
self.context, zone, 'localhost', 53
)
mock_get_serial_number.assert_called_with(
self.service.executor,
self.context,
zone,
'localhost',
53
)

View File

@ -0,0 +1,102 @@
# Copyright 2019 Inspur
#
# Author: ZhouHeng <zhouhenglc@inspur.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 oslo_config import fixture as cfg_fixture
import oslotest.base
from designate import dnsutils
from designate import exceptions
from designate import objects
from designate.tests import fixtures
from designate.worker.tasks import zone as worker_zone
CONF = cfg.CONF
class TestXfr(oslotest.base.BaseTestCase):
def setUp(self):
super(TestXfr, self).setUp()
self.stdlog = fixtures.StandardLogging()
self.useFixture(self.stdlog)
self.useFixture(cfg_fixture.Config(CONF))
self.context = mock.Mock()
@mock.patch.object(dnsutils, 'do_axfr', mock.Mock())
def test_zone_sync_not_change_name(self):
zone = objects.Zone(
id='7592878e-4ade-40de-8b8d-699b871ee6fa',
name='example.com.',
serial=1,
masters=objects.ZoneMasterList.from_list(
[{'host': '127.0.0.1', 'port': 53}, ]
)
)
self.xfr = worker_zone.ZoneXfr(mock.Mock(), self.context, zone)
self.xfr._central_api = mock.Mock()
with mock.patch.object(dnsutils, 'from_dnspython_zone') as mock_dns:
mock_dns.return_value = zone
self.xfr()
self.assertIn('transferred_at', zone.obj_what_changed())
self.assertNotIn('name', zone.obj_what_changed())
@mock.patch.object(dnsutils, 'do_axfr', mock.Mock())
def test_zone_sync_using_list_of_servers(self):
zone = objects.Zone(
id='7592878e-4ade-40de-8b8d-699b871ee6fa',
name='example.com.',
serial=1,
)
self.xfr = worker_zone.ZoneXfr(
mock.Mock(), self.context, zone,
servers=[{'host': '127.0.0.1', 'port': 53}, ]
)
self.xfr._central_api = mock.Mock()
with mock.patch.object(dnsutils, 'from_dnspython_zone') as mock_dns:
mock_dns.return_value = zone
self.xfr()
self.assertIn('transferred_at', zone.obj_what_changed())
self.assertNotIn('name', zone.obj_what_changed())
@mock.patch.object(dnsutils, 'do_axfr', side_effect=exceptions.XFRFailure)
def test_zone_sync_axfr_failure(self, _):
zone = objects.Zone(
id='7592878e-4ade-40de-8b8d-699b871ee6fa',
name='example.com.',
serial=1,
masters=objects.ZoneMasterList.from_list(
[{'host': '127.0.0.1', 'port': 53}, ]
)
)
self.xfr = worker_zone.ZoneXfr(mock.Mock(), self.context, zone)
self.xfr._central_api = mock.Mock()
with mock.patch.object(dnsutils, 'from_dnspython_zone') as mock_dns:
mock_dns.return_value = zone
self.xfr()
self.assertNotIn('transferred_at', zone.obj_what_changed())

View File

@ -35,15 +35,16 @@ class WorkerAPI(object):
API version history:
1.0 - Initial version
1.1 - Added perform_zone_xfr and get_serial_number
"""
RPC_API_VERSION = '1.0'
RPC_API_VERSION = '1.1'
def __init__(self, topic=None):
self.topic = topic if topic else cfg.CONF['service:worker'].topic
target = messaging.Target(topic=self.topic,
version=self.RPC_API_VERSION)
self.client = rpc.get_client(target, version_cap='1.0')
self.client = rpc.get_client(target, version_cap='1.1')
@classmethod
def get_instance(cls):
@ -78,3 +79,13 @@ class WorkerAPI(object):
def start_zone_export(self, context, zone, export):
return self.client.cast(
context, 'start_zone_export', zone=zone, export=export)
def perform_zone_xfr(self, context, zone, servers=None):
return self.client.cast(
context, 'perform_zone_xfr', zone=zone, servers=servers)
def get_serial_number(self, context, zone, host, port):
return self.client.call(
context, 'get_serial_number', zone=zone,
host=host, port=port,
)

View File

@ -42,7 +42,7 @@ class AlsoNotifyTask(object):
class Service(service.RPCService):
RPC_API_VERSION = '1.0'
RPC_API_VERSION = '1.1'
target = messaging.Target(version=RPC_API_VERSION)
@ -143,10 +143,10 @@ class Service(service.RPCService):
def _do_zone_action(self, context, zone):
pool = self.get_pool(zone.pool_id)
all_tasks = []
all_tasks.append(zonetasks.ZoneAction(
self.executor, context, pool, zone, zone.action
))
all_tasks = [
zonetasks.ZoneAction(self.executor, context, pool, zone,
zone.action)
]
# Send a NOTIFY to each also-notifies
for also_notify in pool.also_notifies:
@ -206,3 +206,26 @@ class Service(service.RPCService):
return self.executor.run(zonetasks.ExportZone(
self.executor, context, zone, export
))
@rpc.expected_exceptions()
def perform_zone_xfr(self, context, zone, servers=None):
"""
:param zone: Zone to be exported
:param servers:
:return: None
"""
return self.executor.run(zonetasks.ZoneXfr(
self.executor, context, zone, servers
))
@rpc.expected_exceptions()
def get_serial_number(self, context, zone, host, port):
"""
:param zone: Zone to get serial number
:param host:
:param port:
:return: tuple
"""
return self.executor.run(zonetasks.GetZoneSerial(
self.executor, context, zone, host, port,
))[0]

View File

@ -14,14 +14,18 @@
# License for the specific language governing permissions and limitations
# under the License.
from collections import namedtuple
import errno
import socket
import time
import dns
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
from designate import dnsutils
from designate import exceptions
from designate import objects
from designate import utils
from designate.worker.tasks import base
@ -150,6 +154,36 @@ class SendNotify(base.Task):
return False
class ZoneXfr(base.Task):
"""
Perform AXFR on Zone
"""
def __init__(self, executor, context, zone, servers=None):
super(ZoneXfr, self).__init__(executor)
self.context = context
self.zone = zone
self.servers = servers
def __call__(self):
servers = self.servers or self.zone.masters
if isinstance(servers, objects.ListObjectMixin):
servers = servers.to_list()
try:
dnspython_zone = dnsutils.do_axfr(self.zone.name, servers)
except exceptions.XFRFailure as e:
LOG.warning(e)
return
self.zone.update(dnsutils.from_dnspython_zone(dnspython_zone))
self.zone.transferred_at = timeutils.utcnow()
self.zone.obj_reset_changes(['name'])
self.central_api.update_zone(
self.context, self.zone, increment_serial=False
)
class ZoneActor(base.Task):
"""
Orchestrate the Create/Update/Delete action on targets and update status
@ -558,6 +592,136 @@ class ZonePoller(base.Task):
return result
class GetZoneSerial(base.Task):
"""
Get zone serial number from a resolver using retries.
"""
def __init__(self, executor, context, zone, host, port):
super(GetZoneSerial, self).__init__(executor)
self.context = context
self.zone = zone
self.host = host
self.port = port
self.serial_max_retries = CONF['service:worker'].serial_max_retries
self.serial_retry_delay = CONF['service:worker'].serial_retry_delay
self.serial_timeout = CONF['service:worker'].serial_timeout
def __call__(self):
LOG.debug(
'Sending SOA for zone_name=%(zone)s to %(server)s:%(port)d.',
{
'zone': self.zone.name,
'server': self.host,
'port': self.port,
}
)
actual_serial = None
status = 'ERROR'
for retry in range(0, self.serial_max_retries):
response = self._make_and_send_soa_message(
self.zone.name, self.host, self.port
)
if not response:
pass
elif (response.rcode() in (
dns.rcode.NXDOMAIN,
dns.rcode.REFUSED,
dns.rcode.SERVFAIL) or not bool(response.answer)):
status = 'NO_ZONE'
if (self.zone.serial == 0 and
self.zone.action in ('DELETE', 'NONE')):
actual_serial = 0
break
elif not (response.flags & dns.flags.AA):
LOG.warning(
'Unable to get serial for zone_name=%(zone)s '
'to %(server)s:%(port)d. '
'Unable to get an Authoritative Answer from server.',
{
'zone': self.zone.name,
'server': self.host,
'port': self.port,
}
)
break
elif dns.rcode.from_flags(
response.flags, response.ednsflags) != dns.rcode.NOERROR:
pass
elif (len(response.answer) == 1 and
str(response.answer[0].name) == self.zone.name and
response.answer[0].rdclass == dns.rdataclass.IN and
response.answer[0].rdtype == dns.rdatatype.SOA):
rrset = response.answer[0]
actual_serial = list(rrset.to_rdataset().items)[0].serial
if actual_serial is not None:
status = 'SUCCESS'
break
time.sleep(self.serial_retry_delay)
if actual_serial is None:
LOG.warning(
'Unable to get serial for zone_name=%(zone)s'
'to %(server)s:%(port)d.',
{
'zone': self.zone.name,
'server': self.host,
'port': self.port,
}
)
return status, actual_serial
def _make_and_send_soa_message(self, zone_name, host, port):
"""
Generate and send a SOA message.
:param zone_name: The zone name.
:param host: The destination host for the dns message.
:param port: The destination port for the dns message.
"""
try:
return dnsutils.soa(
zone_name, host, port, timeout=self.serial_timeout
)
except socket.error as e:
if e.errno != errno.EAGAIN:
raise
LOG.info(
'Got EAGAIN while trying to send SOA for '
'zone_name=%(zone_name)s to %(server)s:%(port)d. '
'timeout=%(timeout)d seconds.',
{
'zone_name': zone_name,
'server': host,
'port': port,
'timeout': self.serial_timeout
}
)
except dns.exception.Timeout:
LOG.warning(
'Got Timeout while trying to send SOA for '
'zone_name=%(zone_name)s to %(server)s:%(port)d. '
'timeout=%(timeout)d seconds.',
{
'zone_name': zone_name,
'server': host,
'port': port,
'timeout': self.serial_timeout
}
)
except dns.query.BadResponse:
LOG.warning(
'Got BadResponse while trying to send SOA '
'for zone_name=%(zone_name)s to %(server)s:%(port)d.',
{
'zone_name': zone_name,
'server': host,
'port': port,
}
)
###################
# Status Management
###################

View File

@ -104,8 +104,6 @@ How do I monitor Designate?
Designate can be monitored by various
`monitoring systems listed here <https://wiki.openstack.org/wiki/Operations/Monitoring>`_
OpenStack recommends `Monasca <https://wiki.openstack.org/wiki/Monasca>`_
What are useful metrics to monitor?
-----------------------------------

View File

@ -16,7 +16,6 @@ Contents:
Designate Tempest Plugin <https://docs.openstack.org/designate-tempest-plugin/latest>
architecture
gmr
metrics
sourcedoc/index
ubuntu-dev
integrations

View File

@ -1,14 +0,0 @@
.. _metrics:
****************************
Monasca-Statsd based Metrics
****************************
metrics Base
============
.. automodule:: designate.metrics
:members:
:special-members:
:private-members:
:undoc-members:
:show-inheritance:

View File

@ -4,13 +4,6 @@
MDNS
****
MDNS Base
=========
.. automodule:: designate.mdns.base
:members:
:undoc-members:
:show-inheritance:
MDNS Handler
============
.. automodule:: designate.mdns.handler
@ -18,19 +11,6 @@ MDNS Handler
:undoc-members:
:show-inheritance:
MDNS Notify
===========
.. automodule:: designate.mdns.notify
:members:
:undoc-members:
:show-inheritance:
MDNS RPC API
============
.. automodule:: designate.mdns.rpcapi
:members:
:undoc-members:
:show-inheritance:
MDNS Service
============
@ -38,10 +18,3 @@ MDNS Service
:members:
:undoc-members:
:show-inheritance:
MDNS XFR
========
.. automodule:: designate.mdns.xfr
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,8 @@
---
upgrade:
- |
The ``SECONDARY zone`` RPC calls were moved from the ``mdns`` service to ``worker``
service. When upgrading multi-controller deployments we recommend that you
restart the ``central`` and ``worker`` services first to move the
``SECONDARY zone`` calls to the ``worker``, and once both services has been
upgraded go ahead and restart the ``mdns`` service.

View File

@ -0,0 +1,6 @@
---
upgrade:
- |
Removed the ``monascastatsd`` based metrics solution as all calls using
it has been changed or removed and designate is no longer tracking
any metrics using the metrics endpoint.

View File

@ -48,6 +48,5 @@ python-memcached>=1.56 # PSF
tooz>=1.58.0 # Apache-2.0
debtcollector>=1.19.0 # Apache-2.0
os-win>=4.1.0 # Apache-2.0
monasca-statsd>=1.4.0 # Apache-2.0
futurist>=1.2.0 # Apache-2.0
edgegrid-python>=1.1.1 # Apache-2.0