# Copyright 2014 Rackspace Inc. # # Author: Tim Simmons # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ backend.agent ~~~~~~~~~~~~~ Agent backend for Pool Manager. Sends DNS requests to a remote agent using private OPCODEs to trigger creation / deletion / update of zones. Configured in the [service:pool_manager] section """ import eventlet import dns import dns.rdataclass import dns.rdatatype import dns.exception import dns.flags import dns.rcode import dns.message import dns.opcode from oslo_config import cfg from oslo_log import log as logging from designate.backend import base from designate import exceptions from designate.mdns import rpcapi as mdns_api from designate.utils import DEFAULT_AGENT_PORT import designate.backend.private_codes as pcodes dns_query = eventlet.import_patched('dns.query') LOG = logging.getLogger(__name__) CONF = cfg.CONF class AgentPoolBackend(base.Backend): __plugin_name__ = 'agent' __backend_status__ = 'untested' def __init__(self, target): super(AgentPoolBackend, self).__init__(target) self.host = self.options.get('host', '127.0.0.1') self.port = int(self.options.get('port', DEFAULT_AGENT_PORT)) self.timeout = CONF['service:pool_manager'].poll_timeout self.retry_interval = CONF['service:pool_manager'].poll_retry_interval self.max_retries = CONF['service:pool_manager'].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, retry = self._make_and_send_dns_message( zone.name, self.timeout, pcodes.CC, pcodes.CREATE, pcodes.CLASSCC, self.host, self.port ) if response is None: raise exceptions.Backend("create_zone() failed") def update_zone(self, context, zone): LOG.debug('Update Zone') self.mdns_api.notify_zone_changed( context, zone, self.host, self.port, self.timeout, self.retry_interval, self.max_retries, self.delay ) def delete_zone(self, context, zone): LOG.debug('Delete Zone') response, retry = self._make_and_send_dns_message( zone.name, self.timeout, pcodes.CC, pcodes.DELETE, pcodes.CLASSCC, self.host, self.port ) if response is None: raise exceptions.Backend("failed delete_zone()") def _make_and_send_dns_message(self, zone_name, timeout, opcode, rdatatype, rdclass, dest_ip, dest_port): dns_message = self._make_dns_message(zone_name, opcode, rdatatype, rdclass) retry = 0 response = None LOG.info("Sending '%(msg)s' for '%(zone)s' to '%(server)s:%(port)d'.", {'msg': str(opcode), 'zone': zone_name, 'server': dest_ip, 'port': dest_port}) response = self._send_dns_message( dns_message, dest_ip, dest_port, timeout) if isinstance(response, 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': str(opcode), 'zone': zone_name, 'server': dest_ip, 'port': dest_port, 'timeout': timeout, 'retry': retry}) response = None elif isinstance(response, 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': str(opcode), 'zone': zone_name, 'server': dest_ip, 'port': dest_port, 'timeout': timeout, 'retry': retry}) response = None return (response, retry) # Check that we actually got a NOERROR in the rcode and and an # authoritative answer 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'. Response message: %(resp)s", {'msg': str(opcode), 'zone': zone_name, 'server': dest_ip, 'port': dest_port, 'resp': str(response)}) response = None return (response, retry) else: return (response, retry) return (response, retry) def _make_dns_message(self, zone_name, opcode, rdatatype, rdclass): dns_message = dns.message.make_query(zone_name, rdatatype, rdclass=rdclass) dns_message.flags = 0 dns_message.set_opcode(opcode) dns_message.flags |= dns.flags.AA return dns_message def _send_dns_message(self, dns_message, dest_ip, dest_port, timeout): try: if not CONF['service:mdns'].all_tcp: response = dns_query.udp( dns_message, dest_ip, port=dest_port, timeout=timeout) else: response = dns_query.tcp( dns_message, dest_ip, port=dest_port, timeout=timeout) return response except dns.exception.Timeout as timeout: return timeout except dns_query.BadResponse as badResponse: return badResponse