diff options
author | Erik Olof Gunnar Andersson <eandersson@blizzard.com> | 2022-06-12 15:17:16 -0700 |
---|---|---|
committer | Erik Olof Gunnar Andersson <eandersson@blizzard.com> | 2022-06-23 23:31:41 +0000 |
commit | 8050680948941482d8816c57e81a9fa3110c3818 (patch) | |
tree | d067b0e7bf6b7fd41ca4f56c08e399f9853008d3 | |
parent | e1f7b4d6e327f71bab4a991efdc534e60f01d9a0 (diff) | |
download | designate-8050680948941482d8816c57e81a9fa3110c3818.tar.gz |
Removed RPC calls from MDNS and moved them to the Worker
This patch moved the remaining RPC calls away from the
MDNS service to the Worker and re-worked them to better
match the patterns used in the Worker. This means that
the MDNS service now only handles incoming DNS queries.
In addition the metrics backend has been removed as it was
only used by the MDNS RPC implementation and the monascastatsd
implementation no longer serves a purpose.
Closes-Bug: #1978742
Closes-Bug: #1978743
Change-Id: I5ef106717546a201fd62a51adacd43495c148cd4
44 files changed, 755 insertions, 1296 deletions
diff --git a/designate/agent/handler.py b/designate/agent/handler.py index b32518f0..7f45a1e6 100644 --- a/designate/agent/handler.py +++ b/designate/agent/handler.py @@ -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")) diff --git a/designate/backend/agent.py b/designate/backend/agent.py index 233e90b4..ec44e276 100644 --- a/designate/backend/agent.py +++ b/designate/backend/agent.py @@ -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( diff --git a/designate/backend/base.py b/designate/backend/base.py index 7d8f46b7..2014b3d4 100644 --- a/designate/backend/base.py +++ b/designate/backend/base.py @@ -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): diff --git a/designate/central/service.py b/designate/central/service.py index b31e0143..05173539 100644 --- a/designate/central/service.py +++ b/designate/central/service.py @@ -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 @@ -244,10 +243,6 @@ class Service(service.RPCService): 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): diff --git a/designate/conf/__init__.py b/designate/conf/__init__.py index 7da89cd7..c7acd7f5 100644 --- a/designate/conf/__init__.py +++ b/designate/conf/__init__.py @@ -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) diff --git a/designate/conf/mdns.py b/designate/conf/mdns.py index f2e54501..fb42cc41 100644 --- a/designate/conf/mdns.py +++ b/designate/conf/mdns.py @@ -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'), ] diff --git a/designate/conf/metrics.py b/designate/conf/metrics.py deleted file mode 100644 index dfb788e7..00000000 --- a/designate/conf/metrics.py +++ /dev/null @@ -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 - } diff --git a/designate/conf/worker.py b/designate/conf/worker.py index b4f4bc43..249f0f8c 100644 --- a/designate/conf/worker.py +++ b/designate/conf/worker.py @@ -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'), ] diff --git a/designate/dnsutils.py b/designate/dnsutils.py index be95f512..3dacbd01 100644 --- a/designate/dnsutils.py +++ b/designate/dnsutils.py @@ -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( diff --git a/designate/mdns/base.py b/designate/mdns/base.py deleted file mode 100644 index eaa9ea37..00000000 --- a/designate/mdns/base.py +++ /dev/null @@ -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() diff --git a/designate/mdns/handler.py b/designate/mdns/handler.py index 4e70d68c..b8e7b74f 100644 --- a/designate/mdns/handler.py +++ b/designate/mdns/handler.py @@ -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 diff --git a/designate/mdns/notify.py b/designate/mdns/notify.py deleted file mode 100644 index c349c969..00000000 --- a/designate/mdns/notify.py +++ /dev/null @@ -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 diff --git a/designate/mdns/rpcapi.py b/designate/mdns/rpcapi.py deleted file mode 100644 index 2f8b58c1..00000000 --- a/designate/mdns/rpcapi.py +++ /dev/null @@ -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) diff --git a/designate/mdns/service.py b/designate/mdns/service.py index a369e52d..fa12e6e7 100644 --- a/designate/mdns/service.py +++ b/designate/mdns/service.py @@ -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, diff --git a/designate/mdns/xfr.py b/designate/mdns/xfr.py deleted file mode 100644 index 05c48686..00000000 --- a/designate/mdns/xfr.py +++ /dev/null @@ -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) diff --git a/designate/metrics.py b/designate/metrics.py deleted file mode 100644 index d7418980..00000000 --- a/designate/metrics.py +++ /dev/null @@ -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() diff --git a/designate/metrics_client/noop.py b/designate/metrics_client/noop.py deleted file mode 100644 index 004193d6..00000000 --- a/designate/metrics_client/noop.py +++ /dev/null @@ -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 diff --git a/designate/service.py b/designate/service.py index c7f7550e..6971fb04 100644 --- a/designate/service.py +++ b/designate/service.py @@ -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. diff --git a/designate/tests/test_api/test_v2/test_zones.py b/designate/tests/test_api/test_v2/test_zones.py index 45451d0d..2718cbd6 100644 --- a/designate/tests/test_api/test_v2/test_zones.py +++ b/designate/tests/test_api/test_v2/test_zones.py @@ -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) diff --git a/designate/tests/test_central/test_service.py b/designate/tests/test_central/test_service.py index 7a6156ce..1a729acf 100644 --- a/designate/tests/test_central/test_service.py +++ b/designate/tests/test_central/test_service.py @@ -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() diff --git a/designate/tests/test_mdns/test_handler.py b/designate/tests/test_mdns/test_handler.py index 631df7e4..f598074d 100644 --- a/designate/tests/test_mdns/test_handler.py +++ b/designate/tests/test_mdns/test_handler.py @@ -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') diff --git a/designate/metrics_client/__init__.py b/designate/tests/test_worker/__init__.py index e69de29b..e69de29b 100644 --- a/designate/metrics_client/__init__.py +++ b/designate/tests/test_worker/__init__.py diff --git a/designate/tests/test_mdns/test_notify.py b/designate/tests/test_worker/test_notify.py index 16c7c07e..9bcf2583 100644 --- a/designate/tests/test_mdns/test_notify.py +++ b/designate/tests/test_worker/test_notify.py @@ -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]) diff --git a/designate/tests/unit/mdns/test_handler.py b/designate/tests/unit/mdns/test_handler.py index 93ea162c..e4287cad 100644 --- a/designate/tests/unit/mdns/test_handler.py +++ b/designate/tests/unit/mdns/test_handler.py @@ -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, diff --git a/designate/tests/unit/mdns/test_notify.py b/designate/tests/unit/mdns/test_notify.py deleted file mode 100644 index d0ff4734..00000000 --- a/designate/tests/unit/mdns/test_notify.py +++ /dev/null @@ -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) diff --git a/designate/tests/unit/mdns/test_service.py b/designate/tests/unit/mdns/test_service.py index 4ea8c1d1..d8ca26a9 100644 --- a/designate/tests/unit/mdns/test_service.py +++ b/designate/tests/unit/mdns/test_service.py @@ -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 diff --git a/designate/tests/unit/mdns/test_xfr.py b/designate/tests/unit/mdns/test_xfr.py deleted file mode 100644 index 8aef1b77..00000000 --- a/designate/tests/unit/mdns/test_xfr.py +++ /dev/null @@ -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()) diff --git a/designate/tests/unit/metrics/__init__.py b/designate/tests/unit/metrics/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/designate/tests/unit/metrics/__init__.py +++ /dev/null diff --git a/designate/tests/unit/metrics/test_metrics.py b/designate/tests/unit/metrics/test_metrics.py deleted file mode 100644 index bcb855a7..00000000 --- a/designate/tests/unit/metrics/test_metrics.py +++ /dev/null @@ -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 diff --git a/designate/tests/unit/test_central/test_basic.py b/designate/tests/unit/test_central/test_basic.py index 45918397..e4ec18ba 100644 --- a/designate/tests/unit/test_central/test_basic.py +++ b/designate/tests/unit/test_central/test_basic.py @@ -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( diff --git a/designate/tests/unit/test_dnsutils.py b/designate/tests/unit/test_dnsutils.py index e8c1dbb1..24865d61 100644 --- a/designate/tests/unit/test_dnsutils.py +++ b/designate/tests/unit/test_dnsutils.py @@ -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') diff --git a/designate/tests/unit/workers/test_notify.py b/designate/tests/unit/workers/test_notify.py new file mode 100644 index 00000000..ef151ee3 --- /dev/null +++ b/designate/tests/unit/workers/test_notify.py @@ -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) diff --git a/designate/tests/unit/workers/test_service.py b/designate/tests/unit/workers/test_service.py index 2c1ead77..a97361f8 100644 --- a/designate/tests/unit/workers/test_service.py +++ b/designate/tests/unit/workers/test_service.py @@ -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 + ) diff --git a/designate/tests/unit/workers/test_xfr.py b/designate/tests/unit/workers/test_xfr.py new file mode 100644 index 00000000..a68bd9cc --- /dev/null +++ b/designate/tests/unit/workers/test_xfr.py @@ -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()) diff --git a/designate/worker/rpcapi.py b/designate/worker/rpcapi.py index 03249677..6f7b4d06 100644 --- a/designate/worker/rpcapi.py +++ b/designate/worker/rpcapi.py @@ -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, + ) diff --git a/designate/worker/service.py b/designate/worker/service.py index 98663982..b5d8a622 100644 --- a/designate/worker/service.py +++ b/designate/worker/service.py @@ -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] diff --git a/designate/worker/tasks/zone.py b/designate/worker/tasks/zone.py index 6852df03..6771e7e1 100644 --- a/designate/worker/tasks/zone.py +++ b/designate/worker/tasks/zone.py @@ -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 ################### diff --git a/doc/source/admin/troubleshooting.rst b/doc/source/admin/troubleshooting.rst index bcf72cf8..a91b849f 100644 --- a/doc/source/admin/troubleshooting.rst +++ b/doc/source/admin/troubleshooting.rst @@ -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? ----------------------------------- diff --git a/doc/source/contributor/index.rst b/doc/source/contributor/index.rst index 70807e8c..5eaef0e9 100644 --- a/doc/source/contributor/index.rst +++ b/doc/source/contributor/index.rst @@ -16,7 +16,6 @@ Contents: Designate Tempest Plugin <https://docs.openstack.org/designate-tempest-plugin/latest> architecture gmr - metrics sourcedoc/index ubuntu-dev integrations diff --git a/doc/source/contributor/metrics.rst b/doc/source/contributor/metrics.rst deleted file mode 100644 index 4718b8e7..00000000 --- a/doc/source/contributor/metrics.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _metrics: - -**************************** -Monasca-Statsd based Metrics -**************************** - -metrics Base -============ -.. automodule:: designate.metrics - :members: - :special-members: - :private-members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/contributor/sourcedoc/mdns.rst b/doc/source/contributor/sourcedoc/mdns.rst index 1efbae3c..9badbdd0 100644 --- a/doc/source/contributor/sourcedoc/mdns.rst +++ b/doc/source/contributor/sourcedoc/mdns.rst @@ -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: diff --git a/releasenotes/notes/mdns-rpc-moved-0e7eea194064834a.yaml b/releasenotes/notes/mdns-rpc-moved-0e7eea194064834a.yaml new file mode 100644 index 00000000..8f0a216f --- /dev/null +++ b/releasenotes/notes/mdns-rpc-moved-0e7eea194064834a.yaml @@ -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. diff --git a/releasenotes/notes/removed-metrics-11a53cf88e1ea224.yaml b/releasenotes/notes/removed-metrics-11a53cf88e1ea224.yaml new file mode 100644 index 00000000..7e4a6b65 --- /dev/null +++ b/releasenotes/notes/removed-metrics-11a53cf88e1ea224.yaml @@ -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. diff --git a/requirements.txt b/requirements.txt index fd2bf572..db723ba6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 |