def _send_notify_message(self, context, zone_name, notify_message, dest_ip, dest_port, timeout): """ :param context: The user context. :param zone_name: The zone name for which a NOTIFY needs to be sent. :param notify_message: The notify message that needs to be sent to the slave name servers. :param dest_ip: The destination ip. :param dest_port: The destination port. :param timeout: The timeout in seconds to wait for a response. :return: None """ try: response = dns.query.udp( notify_message, dest_ip, port=dest_port, timeout=timeout) # Check that we actually got a NOERROR in the rcode if dns.rcode.from_flags( response.flags, response.ednsflags) != dns.rcode.NOERROR: LOG.warn(_LW("Failed to get NOERROR while trying to notify " "change in %(zone)s to %(server)s:%(port)d. " "Response message = %(resp)s") % {'zone': zone_name, 'server': dest_ip, 'port': dest_port, 'resp': str(response)}) return response except dns.exception.Timeout as timeout: LOG.warn(_LW("Got Timeout while trying to notify change in" " %(zone)s to %(server)s:%(port)d. ") % {'zone': zone_name, 'server': dest_ip, 'port': dest_port}) return timeout except dns.query.BadResponse as badResponse: LOG.warn(_LW("Got BadResponse while trying to notify " "change in %(zone)s to %(server)s:%(port)d") % {'zone': zone_name, 'server': dest_ip, 'port': dest_port}) return badResponse
def upgrade(migrate_engine): meta.bind = migrate_engine records_table = Table('records', meta, autoload=True) # Create the new inherit_ttl column inherit_ttl = Column('inherit_ttl', Boolean(), default=True) inherit_ttl.create(records_table) # Semi-Populate the new inherit_ttl column. We'll need to do a cross-db # join from powerdns.records -> powerdns.domains -> designate.domains, so # we can't perform the second half here. query = records_table.update().values(inherit_ttl=False) query = query.where(records_table.c.ttl != None) query.execute() # If there are records without an explicity configured TTL, we'll need # a manual post-migration step. query = records_table.select() query = query.where(records_table.c.ttl == None) c = query.count() if c > 0: pmq = ('UPDATE powerdns.records JOIN powerdns.domains ON powerdns.reco' 'rds.domain_id = powerdns.domains.id JOIN designate.domains ON ' 'powerdns.domains.designate_id = designate.domains.id SET power' 'dns.records.ttl = designate.domains.ttl WHERE powerdns.records' '.inherit_ttl = 1;') LOG.warning(_LW('**** A manual post-migration step is required ****')) LOG.warning(_LW('Please issue this query: %s' % pmq))
def start(self): self._coordination_id = ":".join([CONF.host, str(uuid.uuid4())]) if CONF.coordination.backend_url is not None: backend_url = cfg.CONF.coordination.backend_url self._coordinator = tooz.coordination.get_coordinator(backend_url, self._coordination_id) self._coordination_started = False self.tg.add_timer(cfg.CONF.coordination.heartbeat_interval, self._coordinator_heartbeat) self.tg.add_timer(cfg.CONF.coordination.run_watchers_interval, self._coordinator_run_watchers) else: msg = _LW( "No coordination backend configured, distributed " "coordination functionality will be disabled. " "Please configure a coordination backend." ) LOG.warning(msg) super(CoordinationMixin, self).start() if self._coordinator is not None: while not self._coordination_started: try: self._coordinator.start() self._coordinator.create_group(self.service_name) self._coordinator.join_group(self.service_name) self._coordination_started = True except Exception: LOG.warn(_LW("Failed to start Coordinator:"), exc_info=True) time.sleep(15)
def _dns_handle_tcp(self, sock_tcp): LOG.info(_LI("_handle_tcp thread started")) while True: try: client, addr = sock_tcp.accept() if self._service_config.tcp_recv_timeout: client.settimeout(self._service_config.tcp_recv_timeout) LOG.debug("Handling TCP Request from: %(host)s:%(port)d" % {'host': addr[0], 'port': addr[1]}) # Prepare a variable for the payload to be buffered payload = "" # Receive the first 2 bytes containing the payload length expected_length_raw = client.recv(2) (expected_length, ) = struct.unpack('!H', expected_length_raw) # Keep receiving data until we've got all the data we expect while len(payload) < expected_length: data = client.recv(65535) if not data: break payload += data # NOTE: Any uncaught exceptions will result in the main loop # ending unexpectedly. Ensure proper ordering of blocks, and # ensure no exceptions are generated from within. except socket.timeout: client.close() LOG.warning(_LW("TCP Timeout from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]}) except socket.error as e: client.close() errname = errno.errorcode[e.args[0]] LOG.warning( _LW("Socket error %(err)s from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1], 'err': errname}) except struct.error: client.close() LOG.warning(_LW("Invalid packet from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]}) except Exception: client.close() LOG.exception(_LE("Unknown exception handling TCP request " "from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]}) else: # Dispatch a thread to handle the query self.tg.add_thread(self._dns_handle, addr, payload, client=client)
def _make_and_send_dns_message(self, domain_name, timeout, opcode, rdatatype, rdclass, dest_ip, dest_port): dns_message = self._make_dns_message(domain_name, opcode, rdatatype, rdclass) retry = 0 response = None LOG.info(_LI("Sending '%(msg)s' for '%(zone)s' to '%(server)s:" "%(port)d'.") % {'msg': str(opcode), 'zone': domain_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.warn(_LW("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': domain_name, 'server': dest_ip, 'port': dest_port, 'timeout': timeout, 'retry': retry}) response = None elif isinstance(response, dns_query.BadResponse): LOG.warn(_LW("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': domain_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.warn(_LW("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': domain_name, 'server': dest_ip, 'port': dest_port, 'resp': str(response)}) response = None return (response, retry) else: return (response, retry) return (response, retry)
def _allowed(self, request, requester, op, domain_name): if requester not in self.allow_notify: LOG.warn(_LW("%(verb)s for %(name)s from %(server)s refused") % {'verb': op, 'name': domain_name, 'server': requester}) return False return True
def delete_zone(self, context, zone): """ :param context: Security context information. :param zone: Zone to be deleted :return: None """ LOG.info(_LI("Deleting zone %s"), zone.name) results = [] # Delete the zone on each of the Pool Targets for target in self.pool.targets: results.append( self._delete_zone_on_target(context, target, zone)) # TODO(kiall): We should monitor that the Zone is actually deleted # correctly on each of the nameservers, rather than # assuming a successful delete-on-target is OK as we have # in the past. if self._exceed_or_meet_threshold( results.count(True), MAXIMUM_THRESHOLD): LOG.debug('Consensus reached for deleting zone %(zone)s ' 'on pool targets' % {'zone': zone.name}) self.central_api.update_status( context, zone.id, SUCCESS_STATUS, zone.serial) else: LOG.warn(_LW('Consensus not reached for deleting zone %(zone)s' ' on pool targets') % {'zone': zone.name}) self.central_api.update_status( context, zone.id, ERROR_STATUS, zone.serial)
def _handle_notify(self, request): """ Constructs the response to a NOTIFY and acts accordingly on it. * Checks if the master sending the NOTIFY is in the Zone's masters, if not it is ignored. * Checks if SOA query response serial != local serial. """ context = request.environ["context"] response = dns.message.make_response(request) if len(request.question) != 1: response.set_rcode(dns.rcode.FORMERR) yield response raise StopIteration else: question = request.question[0] criterion = {"name": question.name.to_text(), "type": "SECONDARY", "deleted": False} try: zone = self.storage.find_zone(context, criterion) except exceptions.ZoneNotFound: response.set_rcode(dns.rcode.NOTAUTH) yield response raise StopIteration notify_addr = request.environ["addr"][0] # We check if the src_master which is the assumed master for the zone # that is sending this NOTIFY OP is actually the master. If it's not # We'll reply but don't do anything with the NOTIFY. master_addr = zone.get_master_by_ip(notify_addr) if not master_addr: msg = _LW("NOTIFY for %(name)s from non-master server " "%(addr)s, ignoring.") LOG.warning(msg % {"name": zone.name, "addr": notify_addr}) response.set_rcode(dns.rcode.REFUSED) yield response raise StopIteration resolver = dns.resolver.Resolver() # According to RFC we should query the server that sent the NOTIFY resolver.nameservers = [notify_addr] soa_answer = resolver.query(zone.name, "SOA") soa_serial = soa_answer[0].serial if soa_serial == zone.serial: msg = _LI("Serial %(serial)s is the same for master and us for " "%(zone_id)s") LOG.info(msg, {"serial": soa_serial, "zone_id": zone.id}) else: msg = _LI("Scheduling AXFR for %(zone_id)s from %(master_addr)s") info = {"zone_id": zone.id, "master_addr": master_addr} LOG.info(msg, info) self.tg.add_thread(self.zone_sync, context, zone, [master_addr]) response.flags |= dns.flags.AA yield response raise StopIteration
def _dns_handle_udp(self, sock_udp): LOG.info(_LI("_handle_udp thread started")) while True: try: # TODO(kiall): Determine the appropriate default value for # UDP recvfrom. payload, addr = sock_udp.recvfrom(8192) LOG.debug("Handling UDP Request from: %(host)s:%(port)d" % {'host': addr[0], 'port': addr[1]}) # Dispatch a thread to handle the query self.tg.add_thread(self._dns_handle, addr, payload, sock_udp=sock_udp) except socket.error as e: errname = errno.errorcode[e.args[0]] LOG.warning( _LW("Socket error %(err)s from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1], 'err': errname}) except Exception: LOG.exception(_LE("Unknown exception handling UDP request " "from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]})
def _handle_create(self, request): response = dns.message.make_response(request) question = request.question[0] requester = request.environ['addr'][0] zone_name = question.name.to_text() if not self._allowed(request, requester, "CREATE", zone_name): response.set_rcode(dns.rcode.from_text("REFUSED")) return response serial = self.backend.find_zone_serial(zone_name) if serial is not None: LOG.warn(_LW("Not creating %(name)s, zone already exists") % {'name': zone_name}) # Provide an authoritative answer response.flags |= dns.flags.AA return response LOG.debug("Received %(verb)s for %(name)s from %(host)s" % {'verb': "CREATE", 'name': zone_name, 'host': requester}) try: zone = dnsutils.do_axfr(zone_name, self.masters, source=self.transfer_source) self.backend.create_zone(zone) except Exception: response.set_rcode(dns.rcode.from_text("SERVFAIL")) return response # Provide an authoritative answer response.flags |= dns.flags.AA return response
def _call(endpoint, region, *args, **kw): client = get_client(context, endpoint=endpoint) LOG.debug("Attempting to fetch FloatingIPs from %s @ %s" % (endpoint, region)) try: fips = client.list_floatingips(*args, **kw) except neutron_exceptions.Unauthorized as e: # NOTE: 401 might be that the user doesn't have neutron # activated in a particular region, we'll just log the failure # and go on with our lives. LOG.warn(_LW("Calling Neutron resulted in a 401, " "please investigate.")) LOG.exception(e) return except Exception as e: LOG.error(_LE('Failed calling Neutron ' '%(region)s - %(endpoint)s') % {'region': region, 'endpoint': endpoint}) LOG.exception(e) failed.append((e, endpoint, region)) return for fip in fips['floatingips']: data.append({ 'id': fip['id'], 'address': fip['floating_ip_address'], 'region': region }) LOG.debug("Added %i FloatingIPs from %s @ %s" % (len(data), endpoint, region))
def patch_one(self, pool_id): """Update the specific pool""" LOG.warning(_LW("Use of this API Method is DEPRICATED. This will have " "unforseen side affects when used with the " "designate-manage pool commands")) request = pecan.request context = request.environ['context'] body = request.body_dict response = pecan.response if request.content_type == 'application/json-patch+json': raise NotImplemented('json-patch not implemented') # Fetch the existing pool pool = self.central_api.get_pool(context, pool_id) pool = DesignateAdapter.parse('API_v2', body, pool) pool.validate() pool = self.central_api.update_pool(context, pool) LOG.info(_LI("Updated %(pool)s"), {'pool': pool}) response.status_int = 202 return DesignateAdapter.render('API_v2', pool, request=request)
def post_all(self): """Create a Pool""" LOG.warning(_LW("Use of this API Method is DEPRICATED. This will have " "unforseen side affects when used with the " "designate-manage pool commands")) request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict pool = DesignateAdapter.parse('API_v2', body, Pool()) pool.validate() # Create the pool pool = self.central_api.create_pool(context, pool) LOG.info(_LI("Created %(pool)s"), {'pool': pool}) pool = DesignateAdapter.render('API_v2', pool, request=request) response.status_int = 201 response.headers['Location'] = pool['links']['self'] # Prepare and return the response body return pool
def sync_domain(self, context, domain, rdata): """ Re-Sync a DNS domain This is the default, naive, domain synchronization implementation. """ # First up, delete the domain from the backend. try: self.delete_domain(context, domain) except exceptions.DomainNotFound as e: # NOTE(Kiall): This means a domain was missing from the backend. # Good thing we're doing a sync! LOG.warn(_LW("Failed to delete domain '%(domain)s' during sync. " "Message: %(message)s") % {'domain': domain['id'], 'message': str(e)}) # Next, re-create the domain in the backend. self.create_domain(context, domain) # Finally, re-create the records for the domain. for recordset, records in rdata: # Re-create the record in the backend. self.create_recordset(context, domain, recordset) for record in records: self.create_record(context, domain, recordset, record)
def sort_query(query, table, sort_keys, sort_dir=None, sort_dirs=None): if 'id' not in sort_keys: # TODO(justinsb): If this ever gives a false-positive, check # the actual primary key, rather than assuming its id LOG.warning(_LW('Id not in sort_keys; is sort_keys unique?')) assert(not (sort_dir and sort_dirs)) # Default the sort direction to ascending if sort_dirs is None and sort_dir is None: sort_dir = 'asc' # Ensure a per-column sort direction if sort_dirs is None: sort_dirs = [sort_dir for _sort_key in sort_keys] assert(len(sort_dirs) == len(sort_keys)) for current_sort_key, current_sort_dir in zip(sort_keys, sort_dirs): try: sort_dir_func = { 'asc': sqlalchemy.asc, 'desc': sqlalchemy.desc, }[current_sort_dir] except KeyError: raise ValueError(_("Unknown sort direction, " "must be 'desc' or 'asc'")) try: sort_key_attr = getattr(table.c, current_sort_key) except AttributeError: raise utils.InvalidSortKey() query = query.order_by(sort_dir_func(sort_key_attr)) return query, sort_dirs
def _warn_no_backend(self): LOG.warning( _LW( "No coordination backend configured, assuming we are " "the leader. Please configure a coordination backend" ) )
def _create(self, addresses, extra, zone_id, managed=True, resource_type=None, resource_id=None): """ Create a a record from addresses :param addresses: Address objects like {'version': 4, 'ip': '10.0.0.1'} :param extra: Extra data to use when formatting the record :param managed: Is it a managed resource :param resource_type: The managed resource type :param resource_id: The managed resource ID """ if not managed: LOG.warning(_LW( 'Deprecation notice: Unmanaged designate-sink records are ' 'being deprecated please update the call ' 'to remove managed=False')) LOG.debug('Using Zone ID: %s' % zone_id) zone = self.get_zone(zone_id) LOG.debug('Zone: %r' % zone) data = extra.copy() LOG.debug('Event data: %s' % data) data['zone'] = zone['name'] context = DesignateContext().elevated() context.all_tenants = True context.edit_managed_records = True for addr in addresses: event_data = data.copy() event_data.update(self._get_ip_data(addr)) for fmt in cfg.CONF[self.name].get('format'): recordset_values = { 'zone_id': zone['id'], 'name': fmt % event_data, 'type': 'A' if addr['version'] == 4 else 'AAAA'} recordset = self._find_or_create_recordset( context, **recordset_values) record_values = { 'data': addr['address']} if managed: record_values.update({ 'managed': managed, 'managed_plugin_name': self.get_plugin_name(), 'managed_plugin_type': self.get_plugin_type(), 'managed_resource_type': resource_type, 'managed_resource_id': resource_id}) LOG.debug('Creating record in %s / %s with values %r' % (zone['id'], recordset['id'], record_values)) self.central_api.create_record(context, zone['id'], recordset['id'], Record(**record_values))
def _create(self, addresses, extra, zone_id, managed=True, resource_type=None, resource_id=None): """ Create a a record from addresses :param addresses: Address objects like {'version': 4, 'ip': '10.0.0.1'} :param extra: Extra data to use when formatting the record :param managed: Is it a managed resource :param resource_type: The managed resource type :param resource_id: The managed resource ID """ if not managed: LOG.warning( _LW( "Deprecation notice: Unmanaged designate-sink records are " "being deprecated please update the call " "to remove managed=False" ) ) LOG.debug("Using Zone ID: %s", zone_id) zone = self.get_zone(zone_id) LOG.debug("Domain: %r", zone) data = extra.copy() LOG.debug("Event data: %s", data) data["zone"] = zone["name"] context = DesignateContext().elevated() context.all_tenants = True context.edit_managed_records = True for addr in addresses: event_data = data.copy() event_data.update(self._get_ip_data(addr)) for fmt in cfg.CONF[self.name].get("format"): recordset_values = { "zone_id": zone["id"], "name": fmt % event_data, "type": "A" if addr["version"] == 4 else "AAAA", } recordset = self._find_or_create_recordset(context, **recordset_values) record_values = {"data": addr["address"]} if managed: record_values.update( { "managed": managed, "managed_plugin_name": self.get_plugin_name(), "managed_plugin_type": self.get_plugin_type(), "managed_resource_type": resource_type, "managed_resource_id": resource_id, } ) LOG.debug("Creating record in %s / %s with values %r", zone["id"], recordset["id"], record_values) self.central_api.create_record(context, zone["id"], recordset["id"], Record(**record_values))
def _retry_on_deadlock(exc): """Filter to trigger retry a when a Deadlock is received.""" # TODO(kiall): This is a total leak of the SQLA Driver, we'll need a better # way to handle this. if isinstance(exc, db_exception.DBDeadlock): LOG.warning(_LW("Deadlock detected. Retrying...")) return True return False
def upgrade(migrate_engine): meta.bind = migrate_engine LOG.warn(_LW("It will not be possible to downgrade from schema #11")) records_table = Table("records", meta, autoload=True) records_table.c.designate_id.drop() records_table.c.designate_recordset_id.drop()
def upgrade(migrate_engine): meta.bind = migrate_engine domains_table = Table('domains', meta, autoload=True) recordsets_table = Table('recordsets', meta, autoload=True) records_table = Table('records', meta, autoload=True) domains_shard_col = Column('shard', SmallInteger(), nullable=True) domains_shard_col.create(domains_table) recordset_domain_shard_col = Column('domain_shard', SmallInteger(), nullable=True) recordset_domain_shard_col.create(recordsets_table) records_domain_shard_col = Column('domain_shard', SmallInteger(), nullable=True) records_domain_shard_col.create(records_table) def _set_default(): _add_shards( migrate_engine, domains_table, domains_shard_col, domains_table.c.id) _add_shards( migrate_engine, recordsets_table, recordset_domain_shard_col, recordsets_table.c.domain_id) _add_shards( migrate_engine, records_table, records_domain_shard_col, records_table.c.domain_id) def _set_nullable(): domains_table.c.shard.alter(nullable=False) recordsets_table.c.domain_shard.alter(nullable=False) records_table.c.domain_shard.alter(nullable=False) for i in range(0, 5): try: _set_default() _set_nullable() except Exception as e: # The population default & enforcement of nullable=False failed, # try again msg = i18n._LW( "Updating migration for sharding failed, retrying.") LOG.warn(msg) if i >= 4: # Raise if we've reached max attempts causing migration to # fail raise e else: continue # It was successful, no exception so we break the loop. break
def delete_zone(self, context, zone): msg = _LI('Deleting zone %(d_id)s / %(d_name)s') LOG.info(msg, {'d_id': zone['id'], 'd_name': zone['name']}) try: self.client.zones.delete(zone.name) except exceptions.NotFound: msg = _LW("Zone %s not found on remote Designate, Ignoring") LOG.warning(msg, zone.id)
def delete_domain(self, context, domain): msg = _LI('Deleting domain %(d_id)s / %(d_name)s') LOG.info(msg, {'d_id': domain['id'], 'd_name': domain['name']}) try: self.client.zones.delete(domain.name) except exceptions.NotFound: msg = _LW("Zone %s not found on remote Designate, Ignoring") LOG.warn(msg, domain.id)
def __init__(self, keytab, hostname): # store the kerberos credentials in memory rather than on disk os.environ['KRB5CCNAME'] = "MEMORY:" + str(uuid.uuid4()) self.token = None self.keytab = keytab self.hostname = hostname if self.keytab: os.environ['KRB5_CLIENT_KTNAME'] = self.keytab else: LOG.warning(_LW('No IPA client kerberos keytab file given'))
def _handle_udp(self): LOG.info(_LI("_handle_udp thread started")) while True: # TODO(kiall): Determine the appropriate default value for # UDP recvfrom. payload, addr = self._sock_udp.recvfrom(8192) LOG.warn(_LW("Handling UDP Request from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]}) self.tg.add_thread(self._handle, addr, payload)
def _allowed(self, request, requester, op, zone_name): # If there are no explict notifiers specified, allow all if not self.allow_notify: return True if requester not in self.allow_notify: LOG.warn(_LW("%(verb)s for %(name)s from %(server)s refused") % {'verb': op, 'name': zone_name, 'server': requester}) return False return True
def __init__(self, *args, **kwargs): super(CoordinationMixin, self).__init__(*args, **kwargs) self._coordination_id = ":".join([CONF.host, str(uuid.uuid4())]) self._coordinator = None if CONF.coordination.backend_url is not None: self._init_coordination() else: msg = _LW("No coordination backend configured, distributed " "coordination functionality will be disabled." " Please configure a coordination backend.") LOG.warn(msg)
def _handle_tcp(self): LOG.info(_LI("_handle_tcp thread started")) while True: client, addr = self._sock_tcp.accept() LOG.warn(_LW("Handling TCP Request from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]}) payload = client.recv(65535) (expected_length,) = struct.unpack('!H', payload[0:2]) actual_length = len(payload[2:]) # For now we assume all requests are one packet # TODO(vinod): Handle multipacket requests if (expected_length != actual_length): LOG.warn(_LW("got a packet with unexpected length from " "%(host)s:%(port)d. Expected length=%(elen)d. " "Actual length=%(alen)d.") % {'host': addr[0], 'port': addr[1], 'elen': expected_length, 'alen': actual_length}) client.close() else: self.tg.add_thread(self._handle, addr, payload[2:], client)
def _init_extensions(self): """Loads and prepares all enabled extensions""" enabled_notification_handlers = \ cfg.CONF['service:sink'].enabled_notification_handlers notification_handlers = notification_handler.get_notification_handlers( enabled_notification_handlers) if len(notification_handlers) == 0: LOG.warn(_LW('No designate-sink handlers enabled or loaded')) return notification_handlers
def process_request(self, request): # If maintaince mode is not enabled, pass the request on as soon as # possible if not self.enabled: return None # If the caller has the bypass role, let them through if ('context' in request.environ and self.role in request.environ['context'].roles): LOG.warn(_LW('Request authorized to bypass maintenance mode')) return None # Otherwise, reject the request with a 503 Service Unavailable return flask.Response(status=503, headers={'Retry-After': 60})
def _delete(self, managed=True, resource_id=None, resource_type='instance', criterion=None): """ Handle a generic delete of a fixed ip within a domain :param criterion: Criterion to search and destroy records """ if not managed: LOG.warning( _LW('Deprecation notice: Unmanaged designate-sink records are ' 'being deprecated please update the call ' 'to remove managed=False')) criterion = criterion or {} context = DesignateContext.get_admin_context(all_tenants=True) criterion.update({'domain_id': cfg.CONF[self.name].domain_id}) if managed: criterion.update({ 'managed': managed, 'managed_plugin_name': self.get_plugin_name(), 'managed_plugin_type': self.get_plugin_type(), 'managed_resource_id': resource_id, 'managed_resource_type': resource_type }) records = self.central_api.find_records(context, criterion) for record in records: LOG.debug('Deleting record %s' % record['id']) self.central_api.delete_record(context, cfg.CONF[self.name].domain_id, record['recordset_id'], record['id'])
def _dns_handle_tcp(self): LOG.info(_LI("_handle_tcp thread started")) while True: client, addr = self._dns_sock_tcp.accept() if self._service_config.tcp_recv_timeout: client.settimeout(self._service_config.tcp_recv_timeout) LOG.debug("Handling TCP Request from: %(host)s:%(port)d" % { 'host': addr[0], 'port': addr[1] }) # Prepare a variable for the payload to be buffered payload = "" try: # Receive the first 2 bytes containing the payload length expected_length_raw = client.recv(2) (expected_length, ) = struct.unpack('!H', expected_length_raw) # Keep receiving data until we've got all the data we expect while len(payload) < expected_length: data = client.recv(65535) if not data: break payload += data except socket.timeout: client.close() LOG.warn( _LW("TCP Timeout from: %(host)s:%(port)d") % { 'host': addr[0], 'port': addr[1] }) # Dispatch a thread to handle the query self.tg.add_thread(self._dns_handle, addr, payload, client=client)
def _get_listen_on_addresses(self, default_port): """ Helper Method to handle migration from singular host/port to multiple binds """ try: # The API service uses "api_host", and "api_port", others use # just host and port. host = self._service_config.api_host port = self._service_config.api_port except cfg.NoSuchOptError: host = self._service_config.host port = self._service_config.port if host or port is not None: LOG.warning( _LW("host and port config options used, the 'listen' " "option has been ignored")) host = host or "0.0.0.0" # "port" might be 0 to pick a free port, usually during testing port = default_port if port is None else port return [(host, port)] else: def _split_host_port(l): try: host, port = l.split(':', 1) return host, int(port) except ValueError: LOG.exception(_LE('Invalid ip:port pair: %s'), l) raise # Convert listen pair list to a set, to remove accidental # duplicates. return map(_split_host_port, set(self._service_config.listen))
def delete_zone(self, context, zone): LOG.info( _LI('Deleting zone %(d_id)s / %(d_name)s') % { 'd_id': zone['id'], 'd_name': zone['name'] }) url = '/Zone/%s' % zone['name'].rstrip('.') client = self.get_client() try: client.delete(url) except DynClientError as e: if e.http_status == 404: LOG.warn( _LW("Attempt to delete %(d_id)s / %(d_name)s " "caused 404, ignoring.") % { 'd_id': zone['id'], 'd_name': zone['name'] }) pass else: raise client.logout()
def delete_zone(self, context, zone): """ :param context: Security context information. :param zone: Zone to be deleted :return: None """ LOG.info(_LI("Deleting zone %s"), zone.name) results = [] # Delete the zone on each of the Pool Targets for target in self.pool.targets: results.append(self._delete_zone_on_target(context, target, zone)) if not self._exceed_or_meet_threshold(results.count(True), MAXIMUM_THRESHOLD): LOG.warning( _LW('Consensus not reached for deleting zone %(zone)s' ' on pool targets') % {'zone': zone.name}) self.central_api.update_status(context, zone.id, ERROR_STATUS, zone.serial) zone.serial = 0 # Ensure the change has propagated to each nameserver for nameserver in self.pool.nameservers: # See if there is already another update in progress try: self.cache.retrieve(context, nameserver.id, zone.id, DELETE_ACTION) except exceptions.PoolManagerStatusNotFound: update_status = self._build_status_object( nameserver, zone, DELETE_ACTION) self.cache.store(context, update_status) self.mdns_api.poll_for_serial_number(context, zone, nameserver, self.timeout, self.retry_interval, self.max_retries, self.delay)
def syncipaservers2des(servers, designatereq, designateurl): # get existing servers from designate dservers = {} srvurl = designateurl + "/servers" resp = designatereq.get(srvurl) LOG.debug("Response: %s" % pprint.pformat(resp.json())) if resp and resp.status_code == 200 and resp.json() and \ 'servers' in resp.json(): for srec in resp.json()['servers']: dservers[srec['name']] = srec['id'] else: LOG.warn(_LW("No servers in designate")) # first - add servers from ipa not already in designate for server in servers: if server in dservers: LOG.info(_LI("Skipping ipa server %s already in designate"), server) else: desreq = {"name": server} resp = designatereq.post(srvurl, data=json.dumps(desreq)) LOG.debug("Response: %s" % pprint.pformat(resp.json())) if resp.status_code == 200: LOG.info(_LI("Added server %s to designate"), server) else: raise AddServerError("Unable to add %s: %s" % (server, pprint.pformat(resp.json()))) # next - delete servers in designate not in ipa for server, sid in list(dservers.items()): if server not in servers: delresp = designatereq.delete(srvurl + "/" + sid) if delresp.status_code == 200: LOG.info(_LI("Deleted server %s"), server) else: raise DeleteServerError( "Unable to delete %s: %s" % (server, pprint.pformat(delresp.json())))
def __call__(self): LOG.debug('Polling for zone %(zone)s serial %(serial)s on %(ns)s', { 'zone': self.zone.name, 'serial': self.zone.serial, 'ns': self.ns }) try: serial = self._get_serial() LOG.debug( 'Found serial %(serial)d on %(host)s for zone ' '%(zone)s', { 'serial': serial, 'host': self.ns.host, 'zone': self.zone.name }) return serial # TODO(timsim): cache if it's higher than cache except dns.exception.Timeout: LOG.info( _LI('Timeout polling for serial %(serial)d ' '%(host)s for zone %(zone)s'), { 'serial': self.zone.serial, 'host': self.ns.host, 'zone': self.zone.name }) except Exception as e: LOG.warning( _LW('Unexpected failure polling for serial %(serial)d ' '%(host)s for zone %(zone)s. Error: %(error)s'), { 'serial': self.zone.serial, 'host': self.ns.host, 'zone': self.zone.name, 'error': str(e) }) return None
def sort_query(query, table, sort_keys, sort_dir=None, sort_dirs=None): if 'id' not in sort_keys: # TODO(justinsb): If this ever gives a false-positive, check # the actual primary key, rather than assuming its id LOG.warning(_LW('Id not in sort_keys; is sort_keys unique?')) assert (not (sort_dir and sort_dirs)) # Default the sort direction to ascending if sort_dirs is None and sort_dir is None: sort_dir = 'asc' # Ensure a per-column sort direction if sort_dirs is None: sort_dirs = [sort_dir for _sort_key in sort_keys] assert (len(sort_dirs) == len(sort_keys)) for current_sort_key, current_sort_dir in \ six.moves.zip(sort_keys, sort_dirs): try: sort_dir_func = { 'asc': sqlalchemy.asc, 'desc': sqlalchemy.desc, }[current_sort_dir] except KeyError: raise ValueError( _("Unknown sort direction, " "must be 'desc' or 'asc'")) try: sort_key_attr = getattr(table.c, current_sort_key) except AttributeError: raise utils.InvalidSortKey() query = query.order_by(sort_dir_func(sort_key_attr)) return query, sort_dirs
def main(): utils.read_config('designate', sys.argv) logging.setup(CONF, 'designate') gmr.TextGuruMeditation.setup_autorun(version) # NOTE(timsim): This is to ensure people don't start the wrong # services when the worker model is enabled. if cfg.CONF['service:worker'].enabled: LOG.error( _LE('You have designate-worker enabled, starting ' 'designate-zone-manager is incompatible with ' 'designate-worker. You need to start ' 'designate-producer instead.')) sys.exit(1) LOG.warning( _LW('designate-zone-manager is DEPRECATED in favor of ' 'designate-producer, starting designate-producer ' 'under the zone-manager name')) server = producer_service.Service( threads=CONF['service:zone_manager'].threads) service.serve(server, workers=CONF['service:zone_manager'].workers) service.wait()
def _dns_handle_udp(self): LOG.info(_LI("_handle_udp thread started")) while True: try: # TODO(kiall): Determine the appropriate default value for # UDP recvfrom. payload, addr = self._dns_sock_udp.recvfrom(8192) LOG.debug("Handling UDP Request from: %(host)s:%(port)d" % {'host': addr[0], 'port': addr[1]}) # Dispatch a thread to handle the query self.tg.add_thread(self._dns_handle, addr, payload) except socket.error as e: errname = errno.errorcode[e.args[0]] LOG.warn(_LW("Socket error %(err)s from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1], 'err': errname}) except Exception: LOG.exception(_LE("Unknown exception handling UDP request " "from: %(host)s:%(port)d") % {'host': addr[0], 'port': addr[1]})
def create_domain(self, context, domain): """ :param context: Security context information. :param domain: The designate domain object. :return: None """ LOG.debug("Calling create_domain for %s" % domain.name) for server_backend in self.server_backends: server = server_backend['server'] create_status = self._build_status_object(server, domain, CREATE_ACTION) self._create_domain_on_server(context, create_status, domain, server_backend) # ERROR status is updated right away, but success is updated when we # hear back from mdns if self._is_consensus(context, domain, CREATE_ACTION, ERROR_STATUS): LOG.warn( _LW('Consensus not reached ' 'for creating domain %(domain)s') % {'domain': domain.name}) self.central_api.update_status(context, domain.id, ERROR_STATUS, domain.serial)
def post_all(self): """Create a Pool""" LOG.warning(_LW("Use of this API Method is DEPRICATED. This will have " "unforseen side affects when used with the " "designate-manage pool commands")) request = pecan.request response = pecan.response context = request.environ['context'] body = request.body_dict pool = DesignateAdapter.parse('API_v2', body, Pool()) pool.validate() # Create the pool pool = self.central_api.create_pool(context, pool) pool = DesignateAdapter.render('API_v2', pool, request=request) response.status_int = 201 response.headers['Location'] = pool['links']['self'] # Prepare and return the response body return pool
def update_status(self, context, zone, nameserver, status, actual_serial): """ update_status is called by mdns for creates and updates. deletes are handled by the backend entirely and status is determined at the time of delete itself. :param context: Security context information. :param zone: The designate zone object. :param nameserver: The nameserver for which a status update is being sent. :param status: The status, 'SUCCESS' or 'ERROR'. :param actual_serial: The actual serial number received from the name server for the zone. :return: None """ LOG.debug("Calling update_status for %s : %s : %s : %s" % (zone.name, zone.action, status, actual_serial)) action = UPDATE_ACTION if zone.action == 'NONE' else zone.action with lockutils.lock('update-status-%s' % zone.id): try: current_status = self.cache.retrieve( context, nameserver.id, zone.id, action) except exceptions.PoolManagerStatusNotFound: current_status = self._build_status_object( nameserver, zone, action) self.cache.store(context, current_status) cache_serial = current_status.serial_number LOG.debug('For zone %s : %s on nameserver %s the cache serial ' 'is %s and the actual serial is %s.' % (zone.name, action, self._get_destination(nameserver), cache_serial, actual_serial)) if actual_serial and cache_serial <= actual_serial: current_status.status = status current_status.serial_number = actual_serial self.cache.store(context, current_status) consensus_serial = self._get_consensus_serial(context, zone) # If there is a valid consensus serial we can still send a success # for that serial. # If there is a higher error serial we can also send an error for # the error serial. if consensus_serial != 0 and cache_serial <= consensus_serial \ and zone.status != 'ACTIVE': LOG.info(_LI('For zone %(zone)s ' 'the consensus serial is %(consensus_serial)s.') % {'zone': zone.name, 'consensus_serial': consensus_serial}) self.central_api.update_status( context, zone.id, SUCCESS_STATUS, consensus_serial) if status == ERROR_STATUS: error_serial = self._get_error_serial( context, zone, consensus_serial) if error_serial > consensus_serial or error_serial == 0: LOG.warn(_LW('For zone %(zone)s ' 'the error serial is %(error_serial)s.') % {'zone': zone.name, 'error_serial': error_serial}) self.central_api.update_status( context, zone.id, ERROR_STATUS, error_serial) if status == NO_DOMAIN_STATUS and action != DELETE_ACTION: LOG.warn(_LW('Zone %(zone)s is not present in some ' 'targets') % {'zone': zone.name}) self.central_api.update_status( context, zone.id, NO_DOMAIN_STATUS, 0) if consensus_serial == zone.serial and self._is_consensus( context, zone, action, SUCCESS_STATUS, MAXIMUM_THRESHOLD): self._clear_cache(context, zone, action)
def _make_and_send_dns_message(self, zone, host, port, timeout, retry_interval, max_retries, notify=False): """ :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 = retry + 1 LOG.info(_LI("Sending '%(msg)s' for '%(zone)s' to '%(server)s:" "%(port)d'."), {'msg': 'NOTIFY' if notify else 'SOA', 'zone': zone.name, 'server': host, 'port': port}) response = self._send_dns_message( dns_message, host, port, timeout) if isinstance(response, dns.exception.Timeout): LOG.warning( _LW("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}) response = None # retry sending the message if we get a Timeout. time.sleep(retry_interval) continue elif isinstance(response, dns_query.BadResponse): LOG.warning( _LW("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}) response = None break # Check that we actually got a NOERROR in the rcode and and an # authoritative answer elif response.rcode() in (dns.rcode.NXDOMAIN, dns.rcode.REFUSED, dns.rcode.SERVFAIL): LOG.info(_LI("%(zone)s not found on %(server)s:%(port)d"), {'zone': zone.name, 'server': host, 'port': port}) break elif not (response.flags & dns.flags.AA) or dns.rcode.from_flags( response.flags, response.ednsflags) != dns.rcode.NOERROR: LOG.warning( _LW("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 break else: break return (response, retry)
def _dns_handle_tcp_conn(self, addr, client): """ Handle a DNS Query over TCP. Multiple queries can be pipelined though the same TCP connection but they will be processed sequentially. See https://tools.ietf.org/html/draft-ietf-dnsop-5966bis-03 Raises no exception: it's to be run in an eventlet green thread :param addr: Tuple of the client's (IP addr, Port) :type addr: tupple :param client: Client socket :type client: socket :raises: None """ host, port = addr try: # The whole loop lives in a try/except block. On exceptions, the # connection is closed: there would be little chance to save save # the connection after a struct error, a socket error. while True: # Decode the first 2 bytes containing the query length expected_length_raw = client.recv(2) (expected_length, ) = struct.unpack('!H', expected_length_raw) # Keep receiving data until we've got all the data we expect # The buffer contains only one query at a time buf = b'' while len(buf) < expected_length: recv_size = min(expected_length - len(buf), self._TCP_RECV_MAX_SIZE) data = client.recv(recv_size) if not data: break buf += data query = buf # Call into the DNS Application itself with payload and addr for response in self._dns_application({ 'payload': query, 'addr': addr }): # Send back a response only if present if response is None: continue # Handle TCP Responses msg_length = len(response) tcp_response = struct.pack("!H", msg_length) + response client.sendall(tcp_response) except socket.timeout: LOG.info(_LI("TCP Timeout from: %(host)s:%(port)d"), { 'host': host, 'port': port }) except socket.error as e: errname = errno.errorcode[e.args[0]] LOG.warning(_LW("Socket error %(err)s from: %(host)s:%(port)d"), { 'host': host, 'port': port, 'err': errname }) except struct.error: LOG.warning(_LW("Invalid packet from: %(host)s:%(port)d"), { 'host': host, 'port': port }) except Exception: LOG.exception( _LE("Unknown exception handling TCP request " "from: %(host)s:%(port)d"), { 'host': host, 'port': port }) finally: client.close()
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( _LI("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( _LW("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( _LW("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( _LW("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 _handle_notify(self, request): """ Constructs the response to a NOTIFY and acts accordingly on it. * Checks if the master sending the NOTIFY is in the Zone's masters, if not it is ignored. * Checks if SOA query response serial != local serial. """ context = request.environ['context'] response = dns.message.make_response(request) if len(request.question) != 1: response.set_rcode(dns.rcode.FORMERR) yield response raise StopIteration else: question = request.question[0] criterion = { 'name': question.name.to_text(), 'type': 'SECONDARY', 'deleted': False } try: zone = self.storage.find_zone(context, criterion) except exceptions.ZoneNotFound: response.set_rcode(dns.rcode.NOTAUTH) yield response raise StopIteration notify_addr = request.environ['addr'][0] # We check if the src_master which is the assumed master for the zone # that is sending this NOTIFY OP is actually the master. If it's not # We'll reply but don't do anything with the NOTIFY. master_addr = zone.get_master_by_ip(notify_addr) if not master_addr: msg = _LW("NOTIFY for %(name)s from non-master server " "%(addr)s, ignoring.") LOG.warn(msg % {"name": zone.name, "addr": notify_addr}) response.set_rcode(dns.rcode.REFUSED) yield response raise StopIteration resolver = dns.resolver.Resolver() # According to RFC we should query the server that sent the NOTIFY resolver.nameservers = [notify_addr] soa_answer = resolver.query(zone.name, 'SOA') soa_serial = soa_answer[0].serial if soa_serial == zone.serial: msg = _LI("Serial %(serial)s is the same for master and us for " "%(zone_id)s") LOG.info(msg % {"serial": soa_serial, "zone_id": zone.id}) else: msg = _LI("Scheduling AXFR for %(zone_id)s from %(master_addr)s") info = {"zone_id": zone.id, "master_addr": master_addr} LOG.info(msg % info) self.tg.add_thread(self.zone_sync, context, zone, [master_addr]) response.flags |= dns.flags.AA yield response raise StopIteration
def paginate_query(query, table, limit, sort_keys, marker=None, sort_dir=None, sort_dirs=None): if 'id' not in sort_keys: # TODO(justinsb): If this ever gives a false-positive, check # the actual primary key, rather than assuming its id LOG.warning(_LW('Id not in sort_keys; is sort_keys unique?')) assert (not (sort_dir and sort_dirs)) # Default the sort direction to ascending if sort_dirs is None and sort_dir is None: sort_dir = 'asc' # Ensure a per-column sort direction if sort_dirs is None: sort_dirs = [sort_dir for _sort_key in sort_keys] assert (len(sort_dirs) == len(sort_keys)) # Add sorting for current_sort_key, current_sort_dir in zip(sort_keys, sort_dirs): try: sort_dir_func = { 'asc': sqlalchemy.asc, 'desc': sqlalchemy.desc, }[current_sort_dir] except KeyError: raise ValueError( _("Unknown sort direction, " "must be 'desc' or 'asc'")) try: sort_key_attr = getattr(table.c, current_sort_key) except AttributeError: raise utils.InvalidSortKey() query = query.order_by(sort_dir_func(sort_key_attr)) # Add pagination if marker is not None: marker_values = [] for sort_key in sort_keys: v = marker[sort_key] marker_values.append(v) # Build up an array of sort criteria as in the docstring criteria_list = [] for i in range(len(sort_keys)): crit_attrs = [] for j in range(i): table_attr = getattr(table.c, sort_keys[j]) crit_attrs.append((table_attr == marker_values[j])) table_attr = getattr(table.c, sort_keys[i]) if sort_dirs[i] == 'desc': crit_attrs.append((table_attr < marker_values[i])) else: crit_attrs.append((table_attr > marker_values[i])) criteria = sqlalchemy.sql.and_(*crit_attrs) criteria_list.append(criteria) f = sqlalchemy.sql.or_(*criteria_list) query = query.where(f) if limit is not None: query = query.limit(limit) return query
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( _LI("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 = self._send_dns_message(dns_message, host, port, 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( _LW("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( _LW("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( _LW("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 # Check that we actually got a NOERROR in the rcode and and an # authoritative answer if response is None: pass elif (response.rcode() in (dns.rcode.NXDOMAIN, dns.rcode.REFUSED, dns.rcode.SERVFAIL)) or \ (response.rcode() == dns.rcode.NOERROR and not bool(response.answer)): LOG.info( _LI("%(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( _LW("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 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 = 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 = _LW("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 _warn_no_backend(self): LOG.warning(_LW('No coordination backend configure, assuming we are ' 'the only worker. Please configure a coordination ' 'backend'))
def _create(self, addresses, extra, zone_id, managed=True, resource_type=None, resource_id=None): """ Create a a record from addresses :param addresses: Address objects like {'version': 4, 'ip': '10.0.0.1'} :param extra: Extra data to use when formatting the record :param zone_id: The ID of the designate zone. :param managed: Is it a managed resource :param resource_type: The managed resource type :param resource_id: The managed resource ID """ if not managed: LOG.warning(_LW( 'Deprecation notice: Unmanaged designate-sink records are ' 'being deprecated please update the call ' 'to remove managed=False')) LOG.debug('Using Zone ID: %s', zone_id) zone = self.get_zone(zone_id) LOG.debug('Domain: %r', zone) data = extra.copy() LOG.debug('Event data: %s', data) data['zone'] = zone['name'] context = DesignateContext().elevated() context.all_tenants = True context.edit_managed_records = True for addr in addresses: event_data = data.copy() event_data.update(self._get_ip_data(addr)) if addr['version'] == 4: format = self._get_formatv4() else: format = self._get_formatv6() for fmt in format: recordset_values = { 'zone_id': zone['id'], 'name': fmt % event_data, 'type': 'A' if addr['version'] == 4 else 'AAAA'} recordset = self._find_or_create_recordset( context, **recordset_values) record_values = { 'data': addr['address']} if managed: record_values.update({ 'managed': managed, 'managed_plugin_name': self.get_plugin_name(), 'managed_plugin_type': self.get_plugin_type(), 'managed_resource_type': resource_type, 'managed_resource_id': resource_id}) LOG.debug('Creating record in %s / %s with values %r', zone['id'], recordset['id'], record_values) self.central_api.create_record(context, zone['id'], recordset['id'], Record(**record_values))
def _handle_axfr(self, request): context = request.environ['context'] q_rrset = request.question[0] # First check if there is an existing zone # TODO(vinod) once validation is separated from the api, # validate the parameters try: criterion = self._domain_criterion_from_request( request, {'name': q_rrset.name.to_text()}) domain = self.storage.find_domain(context, criterion) except exceptions.DomainNotFound: LOG.warning( _LW("DomainNotFound while handling axfr request. " "Question was %(qr)s") % {'qr': q_rrset}) yield self._handle_query_error(request, dns.rcode.REFUSED) raise StopIteration except exceptions.Forbidden: LOG.warning( _LW("Forbidden while handling axfr request. " "Question was %(qr)s") % {'qr': q_rrset}) yield self._handle_query_error(request, dns.rcode.REFUSED) raise StopIteration # The AXFR response needs to have a SOA at the beginning and end. criterion = {'domain_id': domain.id, 'type': 'SOA'} soa_records = self.storage.find_recordsets_axfr(context, criterion) # Get all the records other than SOA criterion = {'domain_id': domain.id, 'type': '!SOA'} records = self.storage.find_recordsets_axfr(context, criterion) # Place the SOA RRSet at the front and end of the RRSet list records.insert(0, soa_records[0]) records.append(soa_records[0]) # Build the DNSPython RRSets from the Records rrsets = self._prep_rrsets(records, domain.ttl) # Build up a dummy response, we're stealing it's logic for building # the Flags. response = dns.message.make_response(request) response.flags |= dns.flags.AA response.set_rcode(dns.rcode.NOERROR) max_message_size = CONF['service:mdns'].max_message_size if max_message_size > 65535: LOG.warning( _LW('MDNS max message size must not be greater than ' '65535')) max_message_size = 65535 if request.had_tsig: # Make some room for the TSIG RR to be appended at the end of the # rendered message. max_message_size = max_message_size - TSIG_RRSIZE # Render the results, yielding a packet after each TooBig exception. i, renderer = 0, None while i < len(rrsets): # No renderer? Build one if renderer is None: renderer = dns.renderer.Renderer(response.id, response.flags, max_message_size) for q in request.question: renderer.add_question(q.name, q.rdtype, q.rdclass) try: renderer.add_rrset(dns.renderer.ANSWER, rrsets[i]) i += 1 except dns.exception.TooBig: renderer.write_header() if request.had_tsig: # Make the space we reserved for TSIG available for use renderer.max_size += TSIG_RRSIZE renderer.add_tsig(request.keyname, request.keyring[request.keyname], request.fudge, request.original_id, request.tsig_error, request.other_data, request.request_mac, request.keyalgorithm) yield renderer renderer = None if renderer is not None: renderer.write_header() if request.had_tsig: # Make the space we reserved for TSIG available for use renderer.max_size += TSIG_RRSIZE renderer.add_tsig(request.keyname, request.keyring[request.keyname], request.fudge, request.original_id, request.tsig_error, request.other_data, request.request_mac, request.keyalgorithm) yield renderer raise StopIteration
def _handle_axfr(self, request): context = request.environ['context'] q_rrset = request.question[0] # First check if there is an existing zone # TODO(vinod) once validation is separated from the api, # validate the parameters try: criterion = self._zone_criterion_from_request( request, {'name': q_rrset.name.to_text()}) zone = self.storage.find_zone(context, criterion) except exceptions.ZoneNotFound: LOG.warning(_LW("ZoneNotFound while handling axfr request. " "Question was %(qr)s") % {'qr': q_rrset}) yield self._handle_query_error(request, dns.rcode.REFUSED) raise StopIteration except exceptions.Forbidden: LOG.warning(_LW("Forbidden while handling axfr request. " "Question was %(qr)s") % {'qr': q_rrset}) yield self._handle_query_error(request, dns.rcode.REFUSED) raise StopIteration # The AXFR response needs to have a SOA at the beginning and end. criterion = {'zone_id': zone.id, 'type': 'SOA'} soa_records = self.storage.find_recordsets_axfr(context, criterion) # Get all the records other than SOA criterion = {'zone_id': zone.id, 'type': '!SOA'} records = self.storage.find_recordsets_axfr(context, criterion) # Place the SOA RRSet at the front and end of the RRSet list records.insert(0, soa_records[0]) records.append(soa_records[0]) # Build up a dummy response, we're stealing it's logic for building # the Flags. response = dns.message.make_response(request) response.flags |= dns.flags.AA response.set_rcode(dns.rcode.NOERROR) max_message_size = CONF['service:mdns'].max_message_size if max_message_size > 65535: LOG.warning(_LW('MDNS max message size must not be greater than ' '65535')) max_message_size = 65535 if request.had_tsig: # Make some room for the TSIG RR to be appended at the end of the # rendered message. max_message_size = max_message_size - TSIG_RRSIZE # Render the results, yielding a packet after each TooBig exception. i, renderer = 0, None while i < len(records): record = records[i] # No renderer? Build one if renderer is None: renderer = dns.renderer.Renderer( response.id, response.flags, max_message_size) for q in request.question: renderer.add_question(q.name, q.rdtype, q.rdclass) # Build a DNSPython RRSet from the RR rrset = dns.rrset.from_text_list( str(record[3]), # name int(record[2]) if record[2] is not None else zone.ttl, # ttl dns.rdataclass.IN, # class str(record[1]), # rrtype [str(record[4])], # rdata ) try: renderer.add_rrset(dns.renderer.ANSWER, rrset) i += 1 except dns.exception.TooBig: if renderer.counts[dns.renderer.ANSWER] == 0: # We've received a TooBig from the first attempted RRSet in # this packet. Log a warning and abort the AXFR. LOG.warning(_LW('Aborted AXFR of %(zone)s, a single RR ' '(%(rrset_type)s %(rrset_name)s) ' 'exceeded the max message size.'), {'zone': zone.name, 'rrset_type': record[1], 'rrset_name': record[3]}) yield self._handle_query_error(request, dns.rcode.SERVFAIL) raise StopIteration else: yield self._finalize_packet(renderer, request) renderer = None if renderer is not None: yield self._finalize_packet(renderer, request) raise StopIteration
def _handle_record_query(self, request): """Handle a DNS QUERY request for a record""" context = request.environ['context'] response = dns.message.make_response(request) try: q_rrset = request.question[0] # TODO(vinod) once validation is separated from the api, # validate the parameters criterion = { 'name': q_rrset.name.to_text(), 'type': dns.rdatatype.to_text(q_rrset.rdtype), 'zones_deleted': False } recordset = self.storage.find_recordset(context, criterion) try: criterion = self._zone_criterion_from_request( request, {'id': recordset.zone_id}) zone = self.storage.find_zone(context, criterion) except exceptions.ZoneNotFound: LOG.warning(_LW("ZoneNotFound while handling query request" ". Question was %(qr)s") % {'qr': q_rrset}) yield self._handle_query_error(request, dns.rcode.REFUSED) raise StopIteration except exceptions.Forbidden: LOG.warning(_LW("Forbidden while handling query request. " "Question was %(qr)s") % {'qr': q_rrset}) yield self._handle_query_error(request, dns.rcode.REFUSED) raise StopIteration r_rrset = self._convert_to_rrset(zone, recordset) response.set_rcode(dns.rcode.NOERROR) response.answer = [r_rrset] # For all the data stored in designate mdns is Authoritative response.flags |= dns.flags.AA except exceptions.NotFound: # If an FQDN exists, like www.rackspace.com, but the specific # record type doesn't exist, like type SPF, then the return code # would be NOERROR and the SOA record is returned. This tells # caching nameservers that the FQDN does exist, so don't negatively # cache it, but the specific record doesn't exist. # # If an FQDN doesn't exist with any record type, that is NXDOMAIN. # However, an authoritative nameserver shouldn't return NXDOMAIN # for a zone it isn't authoritative for. It would be more # appropriate for it to return REFUSED. It should still return # NXDOMAIN if it is authoritative for a zone but the FQDN doesn't # exist, like abcdef.rackspace.com. Of course, a wildcard within a # zone would mean that NXDOMAIN isn't ever returned for a zone. # # To simply things currently this returns a REFUSED in all cases. # If zone transfers needs different errors, we could revisit this. response.set_rcode(dns.rcode.REFUSED) except exceptions.Forbidden: response.set_rcode(dns.rcode.REFUSED) yield response
def get_serial_number(self, context, zone, host, port, timeout, retry_interval, max_retries, delay): """ :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 = max_retries time.sleep(delay) while (True): (response, retry) = self._make_and_send_dns_message( zone, host, port, timeout, retry_interval, retries) if response and response.rcode() in ( dns.rcode.NXDOMAIN, dns.rcode.REFUSED, dns.rcode.SERVFAIL): status = 'NO_ZONE' 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 = rrset.to_rdataset().items[0].serial if actual_serial is None or actual_serial < zone.serial: # TODO(vinod): Account for serial number wrap around. retries = retries - retry LOG.warning(_LW("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}) if retries > 0: # retry again time.sleep(retry_interval) continue else: break else: # Everything looks good at this point. Return SUCCESS. status = 'SUCCESS' break # Return retries for testing purposes. return (status, actual_serial, retries)
def _dns_handle_tcp(self): LOG.info(_LI("_handle_tcp thread started")) while True: try: client, addr = self._dns_sock_tcp.accept() if self._service_config.tcp_recv_timeout: client.settimeout(self._service_config.tcp_recv_timeout) LOG.debug("Handling TCP Request from: %(host)s:%(port)d" % { 'host': addr[0], 'port': addr[1] }) # Prepare a variable for the payload to be buffered payload = "" # Receive the first 2 bytes containing the payload length expected_length_raw = client.recv(2) (expected_length, ) = struct.unpack('!H', expected_length_raw) # Keep receiving data until we've got all the data we expect while len(payload) < expected_length: data = client.recv(65535) if not data: break payload += data except socket.error as e: client.close() errname = errno.errorcode[e.args[0]] LOG.warning( _LW("Socket error %(err)s from: %(host)s:%(port)d") % { 'host': addr[0], 'port': addr[1], 'err': errname }) except socket.timeout: client.close() LOG.warning( _LW("TCP Timeout from: %(host)s:%(port)d") % { 'host': addr[0], 'port': addr[1] }) except struct.error: client.close() LOG.warning( _LW("Invalid packet from: %(host)s:%(port)d") % { 'host': addr[0], 'port': addr[1] }) except Exception: client.close() LOG.exception( _LE("Unknown exception handling TCP request " "from: %(host)s:%(port)d") % { 'host': addr[0], 'port': addr[1] }) else: # Dispatch a thread to handle the query self.tg.add_thread(self._dns_handle, addr, payload, client=client)
def _warn_no_backend(self): LOG.warning( _LW('No coordination backend configured, assuming we are ' 'the leader. Please configure a coordination backend'))
def _create(self, addresses, extra, managed=True, resource_type=None, resource_id=None): """ Create a a record from addresses :param addresses: Address objects like {'version': 4, 'ip': '10.0.0.1'} :param extra: Extra data to use when formatting the record :param managed: Is it a managed resource :param resource_type: The managed resource type :param resource_id: The managed resource ID """ if not managed: LOG.warning( _LW('Deprecation notice: Unmanaged designate-sink records are ' 'being deprecated please update the call ' 'to remove managed=False')) LOG.debug('Using DomainID: %s' % cfg.CONF[self.name].domain_id) domain = self.get_domain(cfg.CONF[self.name].domain_id) LOG.debug('Domain: %r' % domain) data = extra.copy() LOG.debug('Event data: %s' % data) data['domain'] = domain['name'] context = DesignateContext.get_admin_context(all_tenants=True) for addr in addresses: event_data = data.copy() event_data.update(get_ip_data(addr)) recordset_values = { 'domain_id': domain['id'], 'name': self._get_format() % event_data, 'type': 'A' if addr['version'] == 4 else 'AAAA' } recordset = self._find_or_create_recordset(context, **recordset_values) record_values = {'data': addr['address']} if managed: record_values.update({ 'managed': managed, 'managed_plugin_name': self.get_plugin_name(), 'managed_plugin_type': self.get_plugin_type(), 'managed_resource_type': resource_type, 'managed_resource_id': resource_id }) LOG.debug('Creating record in %s / %s with values %r' % (domain['id'], recordset['id'], record_values)) self.central_api.create_record(context, domain['id'], recordset['id'], Record(**record_values))