Exemplo n.º 1
0
    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))
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
    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)
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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]})
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
        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))
Exemplo n.º 12
0
    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)
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
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
Exemplo n.º 16
0
 def _warn_no_backend(self):
     LOG.warning(
         _LW(
             "No coordination backend configured, assuming we are "
             "the leader. Please configure a coordination backend"
         )
     )
Exemplo n.º 17
0
    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))
Exemplo n.º 18
0
    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))
Exemplo n.º 19
0
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()
Exemplo n.º 21
0
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
Exemplo n.º 22
0
    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)
Exemplo n.º 23
0
    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)
Exemplo n.º 24
0
 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'))
Exemplo n.º 25
0
    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)
Exemplo n.º 26
0
    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
Exemplo n.º 27
0
    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)
Exemplo n.º 28
0
    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)
Exemplo n.º 29
0
    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
Exemplo n.º 30
0
    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})
Exemplo n.º 31
0
    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'])
Exemplo n.º 32
0
    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)
Exemplo n.º 33
0
    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))
Exemplo n.º 34
0
 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()
Exemplo n.º 35
0
    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)
Exemplo n.º 36
0
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())))
Exemplo n.º 37
0
    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
Exemplo n.º 38
0
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
Exemplo n.º 39
0
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()
Exemplo n.º 40
0
    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]})
Exemplo n.º 41
0
    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)
Exemplo n.º 42
0
    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
Exemplo n.º 43
0
    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)
Exemplo n.º 44
0
    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)
Exemplo n.º 45
0
    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()
Exemplo n.º 46
0
    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)
Exemplo n.º 47
0
    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
Exemplo n.º 48
0
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
Exemplo n.º 49
0
    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
Exemplo n.º 50
0
    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
Exemplo n.º 51
0
 def _warn_no_backend(self):
     LOG.warning(_LW('No coordination backend configure, assuming we are '
                     'the only worker. Please configure a coordination '
                     'backend'))
Exemplo n.º 52
0
    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))
Exemplo n.º 53
0
    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
Exemplo n.º 54
0
    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
Exemplo n.º 55
0
    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
Exemplo n.º 56
0
    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)
Exemplo n.º 57
0
    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)
Exemplo n.º 58
0
 def _warn_no_backend(self):
     LOG.warning(
         _LW('No coordination backend configured, assuming we are '
             'the leader. Please configure a coordination backend'))
Exemplo n.º 59
0
    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))