Browse Source

Merge #408 (NSAS Logic)

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac356@3815 e5f2f494-b856-4b98-b285-d166d9295462
Michal Vaner 14 years ago
parent
commit
37d1c3896c
38 changed files with 3328 additions and 988 deletions
  1. 1 1
      doc/Doxyfile
  2. 6 3
      src/lib/nsas/Makefile.am
  3. 46 0
      src/lib/nsas/TODO
  4. 2 1
      src/lib/nsas/address_request_callback.h
  5. 3 0
      src/lib/nsas/asiolink.h
  6. 68 0
      src/lib/nsas/fetchable.h
  7. 4 3
      src/lib/nsas/hash.cc
  8. 1 1
      src/lib/nsas/hash.h
  9. 2 2
      src/lib/nsas/hash_key.cc
  10. 14 6
      src/lib/nsas/hash_key.h
  11. 68 26
      src/lib/nsas/hash_table.h
  12. 1 2
      src/lib/nsas/lru_list.h
  13. 30 0
      src/lib/nsas/nameserver_address.cc
  14. 40 26
      src/lib/nsas/nameserver_address.h
  15. 57 6
      src/lib/nsas/nameserver_address_store.cc
  16. 33 21
      src/lib/nsas/nameserver_address_store.h
  17. 337 198
      src/lib/nsas/nameserver_entry.cc
  18. 131 94
      src/lib/nsas/nameserver_entry.h
  19. 1 1
      src/lib/nsas/nsas_entry.h
  20. 23 6
      src/lib/nsas/nsas_types.h
  21. 7 14
      src/lib/nsas/random_number_generator.h
  22. 80 0
      src/lib/nsas/resolver_interface.h
  23. 2 1
      src/lib/nsas/tests/Makefile.am
  24. 2 2
      src/lib/nsas/tests/address_entry_unittest.cc
  25. 34 0
      src/lib/nsas/tests/fetchable_unittest.cc
  26. 16 13
      src/lib/nsas/tests/hash_deleter_unittest.cc
  27. 26 16
      src/lib/nsas/tests/hash_key_unittest.cc
  28. 36 7
      src/lib/nsas/tests/hash_table_unittest.cc
  29. 19 11
      src/lib/nsas/tests/hash_unittest.cc
  30. 18 18
      src/lib/nsas/tests/lru_list_unittest.cc
  31. 267 20
      src/lib/nsas/tests/nameserver_address_store_unittest.cc
  32. 38 26
      src/lib/nsas/tests/nameserver_address_unittest.cc
  33. 347 394
      src/lib/nsas/tests/nameserver_entry_unittest.cc
  34. 5 5
      src/lib/nsas/tests/nsas_entry_compare_unittest.cc
  35. 212 17
      src/lib/nsas/tests/nsas_test.h
  36. 713 12
      src/lib/nsas/tests/zone_entry_unittest.cc
  37. 527 0
      src/lib/nsas/zone_entry.cc
  38. 111 35
      src/lib/nsas/zone_entry.h

+ 1 - 1
doc/Doxyfile

@@ -568,7 +568,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench ../src/lib/nsas
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 6 - 3
src/lib/nsas/Makefile.am

@@ -15,11 +15,14 @@ libnsas_la_SOURCES += hash_key.cc hash_key.h
 libnsas_la_SOURCES += hash_table.h
 libnsas_la_SOURCES += lru_list.h
 libnsas_la_SOURCES += nameserver_address_store.cc nameserver_address_store.h
-libnsas_la_SOURCES += nameserver_address.h
+libnsas_la_SOURCES += nameserver_address.h nameserver_address.cc
 libnsas_la_SOURCES += nameserver_entry.cc nameserver_entry.h
 libnsas_la_SOURCES += nsas_entry_compare.h
-libnsas_la_SOURCES += nsas_entry.h
+libnsas_la_SOURCES += nsas_entry.h nsas_types.h
+libnsas_la_SOURCES += zone_entry.cc zone_entry.h
+libnsas_la_SOURCES += fetchable.h
+libnsas_la_SOURCES += address_request_callback.h
+libnsas_la_SOURCES += resolver_interface.h
 libnsas_la_SOURCES += random_number_generator.h
-libnsas_la_SOURCES += zone_entry.h
 
 CLEANFILES = *.gcno *.gcda

+ 46 - 0
src/lib/nsas/TODO

@@ -0,0 +1,46 @@
+Sometime soon, before merge, when this stabilizes:
+* Update the wiki page so it matches reality.
+
+Long term:
+* Make a mechanism the cache (which does not exist at the time of writing this
+  note) will be able to notify the NSAS that something has changed (address,
+  new nameserver, etc). Because the cache will have access to the data and
+  knows when it changes (it updates its structures), it is the best place. It
+  will be caching even data like authority and additional sections. It will
+  notify us somehow (we will need to tell it when).
+
+  The changes we need to know about is when set of nameservers or set of
+  addresses for a nameserver change and when a NS record or nameserver's A or
+  AAAA record is explicitly removed from the cache.
+* Optimisation to pass max two outstanding queries on the network (but fetch
+  everything from cache right away). The first can be done by having number of
+  packets on the network, with max of 4 (each query are 2 of them, A and AAAA),
+  if it drops to 2, another one can be send.
+* Add the cache cookies/contexts.
+* Logging.
+* Remove LRU from the nameserver entries, drop them when they are not
+  referenced by any zone entry. This will remove duplicates, keep the RTTs
+  longer and will provide access to everything that exists. This is
+  tricky, though, because we need to be thread safe. There seems to be
+  solution to use weak_ptr inside the hash_table instead of shared_ptr and
+  catch the exception inside get() (and getOrAdd) and delete the dead pointer.
+* Selection algorithm
+* Better way to dispatch all calbacks in a list is needed. Currently, we need
+  to dispatch them outside of a lock. For that reason, we take all of them out
+  into a vector on a stack, leave the lock and start dispatching. This is wrong
+  because when an exception happens inside the callback, we lose the ones
+  not dispatched yet.
+
+  What should be done in this situation anyway? Putting them back? Will anybody
+  still call them?
+
+  Or recommend that if the result is really needed, that destruction of it
+  should be considered failure if it wasn't called yet? Make it the default
+  (eg. signal failure by destruction or call that function from destructor)?
+* Make a zone entry hash table have multiple LRU lists, each one for part of the
+  slots. This will prevent locking contention while still keeping close to
+  the theoretical LRU behaviour (statistically, accesses to each of the part
+  should be as common as to others).
+
+  It might be a good idea to encapsulate the LRUs into the hash table directly
+  (or create a class holding both the hash table and the LRU lists).

+ 2 - 1
src/lib/nsas/address_request_callback.h

@@ -18,6 +18,7 @@
 #define __ADDRESS_REQUEST_CALLBACK_H
 
 #include "asiolink.h"
+#include "nameserver_address.h"
 
 namespace isc {
 namespace nsas {
@@ -55,7 +56,7 @@ public:
     /// This method is used when an address has been retrieved for the request.
     ///
     /// \param address Address to be used to access the nameserver.
-    virtual void success(const asiolink::IOAddress& address) = 0;
+    virtual void success(const NameserverAddress& address) = 0;
 
     /// \brief Unreachable
     ///

+ 3 - 0
src/lib/nsas/asiolink.h

@@ -35,6 +35,9 @@ public:
     IOAddress(const std::string& address_str) : address_(address_str)
     {}
 
+    /// \param Just a virtual destructor
+    virtual ~ IOAddress() { }
+
     /// \return Textual representation of the address
     std::string toText() const
     {return address_;}

+ 68 - 0
src/lib/nsas/fetchable.h

@@ -0,0 +1,68 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $id$
+
+#ifndef __FETCHABLE_H
+#define __FETCHABLE_H
+
+/**
+ * \file fetchable.h
+ * \short Interface of information that can be fetched.
+ */
+
+namespace isc {
+namespace nsas {
+
+/**
+ * \short Interface of information that can be fetched.
+ *
+ * This just holds a state of information that can be fetched from somewhere.
+ * No locking is performed, if it is desirable, it should be locked manually.
+ */
+class Fetchable {
+    public:
+        /// \short States the Fetchable object can be in.
+        enum State {
+            /// \short No one yet asked for the information.
+            NOT_ASKED,
+            /// \short The information is too old and should not be used.
+            EXPIRED,
+            /// \short The information is asked for but it did not arrive.
+            IN_PROGRESS,
+            /// \short It is not possible to get the information.
+            UNREACHABLE,
+            /// \short The information is already present.
+            READY
+        };
+        /// \short Constructors
+        //@{
+        /// This creates the Fetchable object in the given state.
+        Fetchable(State state = NOT_ASKED) :
+            state_(state)
+        { }
+        //@}
+        /// \short Getter and setter of current state.
+        //@{
+        State getState() const { return state_; }
+        void setState(State state) { state_ = state; }
+        //@}
+    private:
+        State state_;
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif // __FETCHABLE_H

+ 4 - 3
src/lib/nsas/hash.cc

@@ -60,7 +60,7 @@ if advised of the possibility of such damage.
 #include <cassert>
 #include <string>
 
-#include "config.h"
+#include <config.h>
 
 #include "hash.h"
 
@@ -72,7 +72,8 @@ namespace nsas {
 // Constructor.
 
 Hash::Hash(uint32_t tablesize, uint32_t maxkeylen, bool randomise) :
-    tablesize_(tablesize), maxkeylen_(min(maxkeylen, (255 - sizeof(uint16_t))))
+    tablesize_(tablesize), maxkeylen_(min<uint32_t>(maxkeylen,
+        (255 - sizeof(uint16_t))))
 {
     // (Code taken from BIND-9)
     //
@@ -150,7 +151,7 @@ uint32_t Hash::operator()(const HashKey& key, bool ignorecase) const
         char        bytes[sizeof(uint16_t)];    // Byte equivalent
     } convert;
 
-    convert.class_code = key.class_code;
+    convert.class_code = key.class_code.getCode();
     for (int j = 0; j < sizeof(uint16_t); ++j, ++i) {
         partial_sum += convert.bytes[j] * randvec_[i];
     }

+ 1 - 1
src/lib/nsas/hash.h

@@ -20,7 +20,7 @@
 #include <stdint.h>
 #include <vector>
 
-#include "exceptions/exceptions.h"
+#include <exceptions/exceptions.h>
 
 #include "hash_key.h"
 

+ 2 - 2
src/lib/nsas/hash_key.cc

@@ -14,9 +14,9 @@
 
 // $Id$
 
-#include <string.h>
+#include <cstring>
 
-#include "config.h"
+#include <config.h>
 #include "hash_key.h"
 
 namespace isc {

+ 14 - 6
src/lib/nsas/hash_key.h

@@ -17,9 +17,11 @@
 #ifndef __HASH_KEY_H
 #define __HASH_KEY_H
 
+#include <dns/rrclass.h>
+
 #include <stdint.h>
 #include <string>
-#include "config.h"
+#include <config.h>
 
 namespace isc {
 namespace nsas {
@@ -51,8 +53,11 @@ struct HashKey {
     /// \param the_key Array of bytes for which key is to be constructed
     /// \param the_keylen Length of the byte array
     /// \param the_class_code Class of this entry
-    HashKey(const char* the_key, uint32_t the_keylen, uint16_t the_class_code) :
-        key(the_key), keylen(the_keylen), class_code(the_class_code)
+    HashKey(const char* the_key, uint32_t the_keylen,
+        const isc::dns::RRClass& the_class_code) :
+        key(the_key),
+        keylen(the_keylen),
+        class_code(the_class_code)
     {}
 
     /// \brief String Constructor
@@ -61,8 +66,11 @@ struct HashKey {
     ///
     /// \param the_key Name to use as the key for the hash
     /// \param the_class_code Class of this entry
-    HashKey(const std::string& the_key, uint16_t the_class_code) :
-        key(the_key.c_str()), keylen(the_key.size()), class_code(the_class_code)
+    HashKey(const std::string& the_key,
+        const isc::dns::RRClass& the_class_code) :
+        key(the_key.c_str()),
+        keylen(the_key.size()),
+        class_code(the_class_code)
     {}
 
     /// \brief Equality
@@ -82,7 +90,7 @@ struct HashKey {
 
     const char* key;        ///< Pointer to the start of the key string
     uint32_t    keylen;     ///< Length of the key string
-    uint16_t    class_code; ///< Class associated with the key
+    isc::dns::RRClass class_code; ///< Class associated with the key
 };
 
 } // namespace nsas

+ 68 - 26
src/lib/nsas/hash_table.h

@@ -24,7 +24,7 @@
 #include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
 #include <list>
 
-#include "config.h"
+#include <config.h>
 
 #include "hash.h"
 #include "hash_key.h"
@@ -160,8 +160,12 @@ public:
     /// \param key Name of the object (and class).  The hash of this is
     /// calculated and used to index the table.
     ///
-    /// \return Shared pointer to the object.
-    virtual boost::shared_ptr<T> get(const HashKey& key);
+    /// \return Shared pointer to the object or NULL if it is not there.
+    boost::shared_ptr<T> get(const HashKey& key) {
+        uint32_t index = hash_(key);
+        sharable_lock lock(table_[index].mutex_);
+        return getInternal(key, index);
+    }
 
     /// \brief Remove Entry
     ///
@@ -173,7 +177,7 @@ public:
     /// calculated and used to index the table.
     ///
     /// \return true if the object was deleted, false if it was not found.
-    virtual bool remove(const HashKey& key);
+    bool remove(const HashKey& key);
 
     /// \brief Add Entry
     ///
@@ -189,16 +193,67 @@ public:
     /// same name already exists, the existing object is replaced.  If false,
     // the addition fails and a status is returned.
     /// \return true if the object was successfully added, false otherwise.
-    virtual bool add(boost::shared_ptr<T>& object, const HashKey& key,
-        bool replace = false);
+    bool add(boost::shared_ptr<T>& object, const HashKey& key,
+        bool replace = false)
+    {
+        uint32_t index = hash_(key);
+        scoped_lock lock(table_[index].mutex_);
+        return addInternal(object, key, index, replace);
+    }
+
+    /**
+     * \brief Attomicly lookup an entry or add a new one if it does not exist.
+     *
+     * Looks up an entry specified by key in the table. If it is not there,
+     * it calls generator() and adds its result to the table under given key.
+     * It is performed attomically to prevent race conditions.
+     *
+     * \param key The entry to lookup.
+     * \param generator will be called when the item is not there. Its result
+     *     will be added and returned. The generator should return as soon
+     *     as possible, the slot is locked during its execution.
+     * \return The boolean part of pair tells if the value was added (true
+     *     means new value, false looked up one). The other part is the
+     *     object, either found or created.
+     * \todo This uses a scoped_lock, which does not allow sharing and is
+     *     used a lot in the code. It might turn out in future that it is a
+     *     problem and that most of the accesses is read only. In that case we
+     *     could split it to fast-slow path - first try to find it with
+     *     shared_lock. If it fails, lock by scoped_lock, try to find again (we
+     *     unlocked it, so it might have appeared) and if it still isn't there,
+     *     create it. Not implemented now as it might or might not help (it
+     *     could even slow it down) and the code would get more complicated.
+     */
+    template<class Generator>
+    std::pair<bool, boost::shared_ptr<T> > getOrAdd(const HashKey& key,
+        const Generator& generator)
+    {
+        uint32_t index = hash_(key);
+        scoped_lock lock(table_[index].mutex_);
+        boost::shared_ptr<T> result(getInternal(key, index));
+        if (result) {
+            return (std::pair<bool, boost::shared_ptr<T> >(false, result));
+        } else {
+            result = generator();
+            addInternal(result, key, index);
+            return (std::pair<bool, boost::shared_ptr<T> >(true, result));
+        }
+    }
 
     /// \brief Returns Size of Hash Table
     ///
     /// \return Size of hash table
-    virtual uint32_t tableSize() const {
+    uint32_t tableSize() const {
         return table_.size();
     }
 
+protected:
+    // Internal parts, expect to be already locked
+    boost::shared_ptr<T> getInternal(const HashKey& key,
+        uint32_t index);
+    bool addInternal(boost::shared_ptr<T>& object, const HashKey& key,
+        uint32_t index, bool replace = false);
+
 private:
     Hash                             hash_;  ///< Hashing function
     std::vector<HashTableSlot<T> >   table_; ///< The hash table itself
@@ -214,15 +269,9 @@ HashTable<T>::HashTable(HashTableCompare<T>* compare, uint32_t size) :
 
 // Lookup an object in the table
 template <typename T>
-boost::shared_ptr<T> HashTable<T>::get(const HashKey& key) {
-
-    // Calculate the hash value
-    uint32_t index = hash_(key);
-
-    // Take out a read lock on this hash slot.  The lock is released when this
-    // object goes out of scope.
-    sharable_lock lock(table_[index].mutex_);
-
+boost::shared_ptr<T> HashTable<T>::getInternal(const HashKey& key,
+    uint32_t index)
+{
     // Locate the object.
     typename HashTableSlot<T>::iterator i;
     for (i = table_[index].list_.begin(); i != table_[index].list_.end(); ++i) {
@@ -267,17 +316,10 @@ bool HashTable<T>::remove(const HashKey& key) {
 
 // Add an entry to the hash table
 template <typename T>
-bool HashTable<T>::add(boost::shared_ptr<T>& object, const HashKey& key,
-    bool replace)
+bool HashTable<T>::addInternal(boost::shared_ptr<T>& object,
+    const HashKey& key, uint32_t index, bool replace)
 {
-
-    // Calculate the hash value
-    uint32_t index = hash_(key);
-
-    // Access to the elements of this hash slot are accessed under a mutex.
-    scoped_lock lock(table_[index].mutex_);
-
-    // Now search this list to see if the element already exists.
+    // Search this list to see if the element already exists.
     typename HashTableSlot<T>::iterator i;
     for (i = table_[index].list_.begin(); i != table_[index].list_.end(); ++i) {
         if ((*compare_)(i->get(), key)) {

+ 1 - 2
src/lib/nsas/lru_list.h

@@ -25,8 +25,7 @@
 #include <boost/thread.hpp>
 #include <boost/interprocess/sync/scoped_lock.hpp>
 
-#include "config.h"
-
+#include <config.h>
 
 namespace isc {
 namespace nsas {

+ 30 - 0
src/lib/nsas/nameserver_address.cc

@@ -0,0 +1,30 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $id$
+
+#include "nameserver_address.h"
+#include "nameserver_entry.h"
+
+namespace isc {
+namespace nsas {
+
+void
+NameserverAddress::updateRTT(uint32_t rtt) const {
+    // We delegate it to the address entry inside the nameserver entry
+    ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+}
+
+} // namespace nsas
+} // namespace isc

+ 40 - 26
src/lib/nsas/nameserver_address.h

@@ -19,19 +19,26 @@
 
 #include <boost/shared_ptr.hpp>
 
+#include <exceptions/exceptions.h>
+
 #include "asiolink.h"
-#include "nameserver_entry.h"
+#include "address_entry.h"
+#include "nsas_types.h"
 
 namespace isc {
 namespace nsas {
 
+class ZoneEntry;
+class NameserverEntry;
+
 /// \brief Empty \c NameserverEntry pointer exception
 ///
 /// Thrown if the the \c NameservrEntry pointer in the \c boost::shared_ptr that passed
 /// into \c NameserverAddress' constructor is NULL
 class NullNameserverEntryPointer : public isc::Exception {
 public:
-    NullNameserverEntryPointer(const char* file, size_t line, const char* what) :
+    NullNameserverEntryPointer(const char* file, size_t line,
+        const char* what) :
         isc::Exception(file, line, what)
     {}
 };
@@ -39,49 +46,43 @@ public:
 /// \brief Nameserver Address
 ///
 /// This class implements the object that returned from NSAS when the resolver
-/// request an address for the name server. It contains one IOAddress object
+/// request an address for the name server. It contains one address
 /// that can be used by resolver. When the resolver get query back from the name
-/// server, it should update the name server's RTT(Round Trip Time) with this 
+/// server, it should update the name server's RTT(Round Trip Time) with this
 /// object.
+///
+/// It is not thread safe, only reentrant. It is expected to be kept inside
+/// the resolver and used only once for the address and once for the update.
 
 class NameserverAddress {
 public:
     /// \brief Constructor
     ///
     /// The NameserverAddress object will contain one shared_ptr object that
-    /// pointed to NameserverEntry which contains the address as well as it's 
+    /// pointed to NameserverEntry which contains the address as well as it's
     /// corresponding index. The user can update it's RTT with the index later.
     ///
     /// \param namerserver A shared_ptr that points to a NameserverEntry object
     /// the shared_ptr can avoid the NameserverEntry object being dropped while the
     /// request is processing.
     /// \param index The address's index in NameserverEntry's addresses vector
-    /// \param family Address family, AF_INET or AF_INET6
-    NameserverAddress(boost::shared_ptr<NameserverEntry>& nameserver, uint32_t index, short family):
-        ns_(nameserver), index_(index), family_(family)
+    /// \param family Address family, V4_ONLY or V6_ONLY
+    NameserverAddress(const boost::shared_ptr<NameserverEntry>& nameserver,
+        const AddressEntry& address, AddressFamily family):
+        ns_(nameserver), address_(address), family_(family)
     {
-        if(!ns_.get()) {
+        if(!ns_) {
             isc_throw(NullNameserverEntryPointer, "NULL NameserverEntry pointer.");
         }
     }
 
     /// \brief Default Constructor
-    ///
-    NameserverAddress(): index_(0), family_(AF_INET)
-    {
-    }
-
-    /// \brief Destructor
-    ///
-    /// Empty destructor.
-    ~NameserverAddress()
-    {
-    }
+    NameserverAddress() : address_(asiolink::IOAddress("::1")) { }
 
     /// \brief Return address
     ///
-    asiolink::IOAddress getAddress() const { 
-        return ns_.get()->getAddressAtIndex(index_, family_); 
+    asiolink::IOAddress getAddress() const {
+        return (address_.getAddress());
     }
 
     /// \brief Update Round-trip Time
@@ -89,14 +90,27 @@ public:
     /// When the user get one request back from the name server, it should
     /// update the address's RTT.
     /// \param rtt The new Round-Trip Time
-    void updateRTT(uint32_t rtt) { 
-        ns_.get()->updateAddressRTTAtIndex(rtt, index_, family_); 
+    void updateRTT(uint32_t rtt) const;
+
+    /// Short access to the AddressEntry inside.
+    //@{
+    const AddressEntry& getAddressEntry() const {
+        return (address_);
+    }
+    AddressEntry& getAddressEntry() {
+        return (address_);
     }
+    //@}
 private:
 
+    /*
+     * Note: Previous implementation used index into the entry. That is wrong,
+     * as the list of addresses may change. Thil would cause setting a
+     * different address or a crash.
+     */
     boost::shared_ptr<NameserverEntry> ns_;  ///< Shared-pointer to NameserverEntry object
-    uint32_t index_;                         ///< The address index in NameserverEntry
-    short family_;                           ///< Address family AF_INET or AF_INET6
+    AddressEntry address_;            ///< The address
+    AddressFamily family_;                   ///< The address family (V4_ONLY or V6_ONLY)
 };
 
 } // namespace nsas

+ 57 - 6
src/lib/nsas/nameserver_address_store.cc

@@ -14,15 +14,25 @@
 
 // $Id$
 
+#include <boost/thread.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
 
-#include "config.h"
+#include <config.h>
+#include <dns/rdataclass.h>
 
+#include "hash_table.h"
+#include "lru_list.h"
 #include "hash_deleter.h"
 #include "nsas_entry_compare.h"
 #include "nameserver_entry.h"
 #include "nameserver_address_store.h"
 #include "zone_entry.h"
+#include "address_request_callback.h"
+
+using namespace isc::dns;
+using namespace std;
+using namespace boost;
 
 namespace isc {
 namespace nsas {
@@ -32,13 +42,54 @@ namespace nsas {
 // The LRU lists are set equal to three times the size of the respective
 // hash table, on the assumption that three elements is the longest linear
 // search we want to do when looking up names in the hash table.
-NameserverAddressStore::NameserverAddressStore(uint32_t zonehashsize,
+NameserverAddressStore::NameserverAddressStore(
+    shared_ptr<ResolverInterface> resolver, uint32_t zonehashsize,
     uint32_t nshashsize) :
-    zone_hash_(new NsasEntryCompare<ZoneEntry>, zonehashsize),
-    nameserver_hash_(new NsasEntryCompare<NameserverEntry>, nshashsize),
-    zone_lru_((3 * zonehashsize), new HashDeleter<ZoneEntry>(zone_hash_)),
-    nameserver_lru_((3 * nshashsize), new HashDeleter<NameserverEntry>(nameserver_hash_))
+    zone_hash_(new HashTable<ZoneEntry>(new NsasEntryCompare<ZoneEntry>,
+        zonehashsize)),
+    nameserver_hash_(new HashTable<NameserverEntry>(
+        new NsasEntryCompare<NameserverEntry>, nshashsize)),
+    zone_lru_(new LruList<ZoneEntry>((3 * zonehashsize),
+        new HashDeleter<ZoneEntry>(*zone_hash_))),
+    nameserver_lru_(new LruList<NameserverEntry>((3 * nshashsize),
+        new HashDeleter<NameserverEntry>(*nameserver_hash_))),
+    resolver_(resolver)
+{ }
+
+namespace {
+
+/*
+ * We use pointers here so there's no call to any copy constructor.
+ * It is easier for the compiler to inline it and prove that there's
+ * no need to copy anything. In that case, the bind should not be
+ * called at all to create the object, just call the function.
+ */
+shared_ptr<ZoneEntry>
+newZone(const shared_ptr<ResolverInterface>* resolver, const string* zone,
+    const RRClass* class_code,
+    const shared_ptr<HashTable<NameserverEntry> >* ns_hash,
+    const shared_ptr<LruList<NameserverEntry> >* ns_lru)
+{
+    shared_ptr<ZoneEntry> result(new ZoneEntry(*resolver, *zone, *class_code,
+        *ns_hash, *ns_lru));
+    return (result);
+}
+
+}
+
+void
+NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
+    shared_ptr<AddressRequestCallback> callback, AddressFamily family)
 {
+    pair<bool, shared_ptr<ZoneEntry> > zone_obj(zone_hash_->getOrAdd(HashKey(
+        zone, class_code), boost::bind(newZone, &resolver_, &zone, &class_code,
+        &nameserver_hash_, &nameserver_lru_)));
+    if (zone_obj.first) {
+        zone_lru_->add(zone_obj.second);
+    } else {
+        zone_lru_->touch(zone_obj.second);
+    }
+    zone_obj.second->addCallback(callback, family);
 }
 
 } // namespace nsas

+ 33 - 21
src/lib/nsas/nameserver_address_store.h

@@ -20,17 +20,26 @@
 #include <string>
 #include <vector>
 
-#include "rrset.h"
+#include <boost/shared_ptr.hpp>
 
-#include "address_request_callback.h"
-#include "hash_table.h"
-#include "nameserver_entry.h"
-#include "lru_list.h"
-#include "zone_entry.h"
+#include "nsas_types.h"
 
 namespace isc {
+// Some forward declarations, so we do not need to include so many headers
+
+namespace dns {
+class RRClass;
+}
+
 namespace nsas {
 
+class ResolverInterface;
+template<class T> class HashTable;
+template<class T> class LruList;
+class ZoneEntry;
+class NameserverEntry;
+class AddressRequestCallback;
+
 /// \brief Nameserver Address Store
 ///
 /// This class implements the bare bones of the nameserver address store - the
@@ -46,15 +55,17 @@ public:
     /// The constructor sizes all the tables.  As there are various
     /// relationships between the table sizes, and as some values are best as
     /// prime numbers, the table sizes are determined by compile-time values.
-    /// 
+    ///
+    /// \param resolver Which resolver object (or resolver-like, in case of
+    /// tests) should it use to ask questions.
     /// \param zonehashsize Size of the zone hash table.  The default value of
     /// 1009 is the first prime number above 1000.
     /// \param nshash size Size of the nameserver hash table.  The default
-    /// value of 2003 is the first prime number over 2000, and by implication,
+    /// value of 3001 is the first prime number over 3000, and by implication,
     /// there is an assumption that there will be more nameservers than zones
     /// in the store.
-    NameserverAddressStore(uint32_t zonehashsize = 1009,
-        uint32_t nshashsize = 3001);
+    NameserverAddressStore(boost::shared_ptr<ResolverInterface> resolver,
+        uint32_t zonehashsize = 1009, uint32_t nshashsize = 3001);
 
     /// \brief Destructor
     ///
@@ -67,15 +78,13 @@ public:
     /// Looks up the address of a nameserver in the zone.
     ///
     /// \param zone Name of zone for which an address is required.
-    /// \param authority Authority RRset from the referral containing the
-    /// nameservers that serve the zone.
-    /// \param additional Additional RRset(s) for authority information.  These
-    /// are taken from the referral.
+    /// \param class_code Class of the zone.
     /// \param callback Callback object used to pass the result back to the
     /// caller.
-    /* void lookup(const std::string& zone, isc::dns::AbstractRRset& authority,
-        const std::vector<isc::dns::AbstractRRset>& additional
-        boost::shared_ptr<isc::dns::AddressRequestCallback> callback ); */
+    /// \param family Which address is requested.
+    void lookup(const std::string& zone, const dns::RRClass& class_code,
+        boost::shared_ptr<AddressRequestCallback> callback, AddressFamily
+        family = ANY_OK);
 
     /// \brief Protected Members
     ///
@@ -90,12 +99,15 @@ public:
     //@{
 protected:
     // Zone and nameserver hash tables
-    HashTable<ZoneEntry>        zone_hash_;
-    HashTable<NameserverEntry>  nameserver_hash_;
+    boost::shared_ptr<HashTable<ZoneEntry> > zone_hash_;
+    boost::shared_ptr<HashTable<NameserverEntry> > nameserver_hash_;
 
     // ... and the LRU lists
-    LruList<ZoneEntry>          zone_lru_;
-    LruList<NameserverEntry>    nameserver_lru_;
+    boost::shared_ptr<LruList<ZoneEntry> > zone_lru_;
+    boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
+    // The resolver we use
+private:
+    boost::shared_ptr<ResolverInterface> resolver_;
     //}@
 };
 

+ 337 - 198
src/lib/nsas/nameserver_entry.cc

@@ -18,20 +18,24 @@
 #include <functional>
 #include <cassert>
 #include <iostream>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
 
 #include <ctype.h>
 #include <strings.h>
 
-#include "config.h"
+#include <config.h>
 
-#include "exceptions/exceptions.h"
-#include "name.h"
-#include "rrclass.h"
-#include "rrttl.h"
+#include <exceptions/exceptions.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/question.h>
 
 #include "address_entry.h"
 #include "nameserver_address.h"
 #include "nameserver_entry.h"
+#include "resolver_interface.h"
 
 using namespace asiolink;
 using namespace isc::nsas;
@@ -42,246 +46,381 @@ using namespace boost;
 namespace isc {
 namespace nsas {
 
-// Constructor, initialized with the list of addresses associated with this
-// nameserver.
-NameserverEntry::NameserverEntry(const AbstractRRset* v4Set,
-    const AbstractRRset* v6Set, time_t curtime) : expiration_(0)
-{
-    uint32_t rtt = 1;       // Round-trip time for an address
-    string v4name = "";     // Name from the V4 RRset
-    string v6name = "";     // Name from the v6 RRset
-    uint16_t v4class = 0;   // Class of V4 RRset
-    uint16_t v6class = 0;   // Class for V6 RRset
-
-    // Get the time for setting the expiration time.
-    if (curtime == 0) {
-        curtime = time(NULL);
-    }
+namespace {
 
-    // Add the v4 addresses to the list of addresses.  Each address is assigned
-    // a small RTT that ensures that each server is used at least once (in a
-    // random order).
+// Just shorter type alias
+typedef recursive_mutex::scoped_lock Lock;
 
+}
 
-    // Do the V4 addresses first
-    // XXX: Do we need to check that these are V4 addresses?
-    if (v4Set) {
-        RdataIteratorPtr i = v4Set->getRdataIterator();
-        i->first();
-        while (! i->isLast()) {
-            v4_addresses_.push_back(AddressEntry(IOAddress(i->getCurrent().toText()),
-            rtt));
-            i->next();
-        }
+// Returns the list of addresses matching the given family
+Fetchable::State
+NameserverEntry::getAddresses(AddressVector& addresses,
+    AddressFamily family, bool expired_ok)
+{
+    Lock lock(mutex_);
+
+    // Check TTL
+    time_t now(time(NULL));
+    // We take = as well, so we catch TTL 0 correctly
+    // expiration_ == 0 means not set, the reason is we are UNREACHABLE or
+    // NOT_ASKED or IN_PROGRESS
+    if (getState() != NOT_ASKED && expiration_ && expiration_ <= now) {
+        setState(EXPIRED);
+    }
 
-        // Set the expiration time and extract the owner name and class
-        expiration_ = curtime + v4Set->getTTL().getValue();
-        v4name = v4Set->getName().toText(false);    // Ensure trailing dot
-        v4class = v4Set->getClass().getCode();
+    if (getState() == EXPIRED && !expired_ok) {
+        return EXPIRED;
+    }
 
-        // Update the address selector
-        updateAddressSelector(v4_addresses_, v4_address_selector_);
+    switch (getState()) {
+        case IN_PROGRESS:
+            /*
+             * Did we receive the address already?
+             *
+             * We might have already received the addresses for this family
+             * and still wait for the other (in which case has_address_[family]
+             * will be true). We might already received a negative answer,
+             * in which case expect_address_[family] is false and
+             * has_address_[family] is false as well.
+             */
+            if (!has_address_[family] && expect_address_[family]) {
+                return IN_PROGRESS;
+            }
+            // If we do not expect the address, then fall trough to READY
+        case EXPIRED: // If expired_ok, we pretend to be ready
+        case READY:
+            if (!has_address_[family]) {
+                return UNREACHABLE;
+            }
+            break; // OK, we give some answers
+        case NOT_ASKED:
+        case UNREACHABLE:
+            // Reject giving any data
+            return (getState());
     }
 
-    // Now the v6 addresses
-    // XXX: Do we need to check that these are V6 addresses?
-    if (v6Set) {
-        RdataIteratorPtr i = v6Set->getRdataIterator();
-        i->first();
-        while (! i->isLast()) {
-            v6_addresses_.push_back(AddressEntry(IOAddress(i->getCurrent().toText()),
-            rtt));
-            i->next();
+    shared_ptr<NameserverEntry> self(shared_from_this());
+    // If any address is OK, just pass everything we have
+    if (family == ANY_OK) {
+        BOOST_FOREACH(const AddressEntry& entry, addresses_[V6_ONLY]) {
+            addresses.push_back(NameserverAddress(self, entry, V6_ONLY));
         }
-
-        // Update the expiration time of the data
-        time_t v6expiration = curtime + v6Set->getTTL().getValue();
-        if (expiration_ == 0) {
-            expiration_ = v6expiration;
+        BOOST_FOREACH(const AddressEntry& entry, addresses_[V4_ONLY]) {
+            addresses.push_back(NameserverAddress(self, entry, V4_ONLY));
         }
-        else {
-            expiration_ = min(expiration_, v6expiration);
+    } else {
+        BOOST_FOREACH(const AddressEntry& entry, addresses_[family]) {
+            addresses.push_back(NameserverAddress(self, entry, family));
         }
-
-        // Extract the name of the v6 set and its class
-        v6name = v6Set->getName().toText(false);    // Ensure trailing dot
-        v6class = v6Set->getClass().getCode();
-
-        // Update the address selector
-        updateAddressSelector(v6_addresses_, v6_address_selector_);
-    }
-
-    // TODO: Log a problem if both V4 and V6 address were null.
-
-    if (v4Set && v6Set) {
-
-        // If two owner names were specified and they were different, something
-        // has gone wrong with the logic that created this object.
-       if (strcasecmp(v4name.c_str(), v6name.c_str()) != 0) {
-           isc_throw(InconsistentOwnerNames,
-               "Owner names for NameserverEntry RRsets are different");
-       }
-
-       // Likewise with the class
-       if (v4class != v6class) {
-           isc_throw(InconsistentClass,
-               "Class codes for NameserverEntry RRsets are different");
-       }
     }
-
-    // Otherwise set the owner name
-    name_ = v4Set ? v4name : v6name;
-    classCode_ = v4Set ? v4class : v6class;
-}
-
-// Returns the list of addresses matching the given family
-void NameserverEntry::getAddresses(AddressVector& addresses, short family) const {
-
-    // Quick check that allows validation of the assumption in the header file
-    // that a family value of 0 will select all address families.
-    assert(AF_INET != 0);
-    assert(AF_INET6 != 0);
-    
-    // Now copy all entries that meet the criteria.  Since remove_copy_if
-    // does the inverse (copies all entries that do not meet the criteria),
-    // the predicate for address selection is negated.
-    remove_copy_if(v4_addresses_.begin(), v4_addresses_.end(), back_inserter(addresses),
-        bind1st(AddressSelection(), family));
-    remove_copy_if(v6_addresses_.begin(), v6_addresses_.end(), back_inserter(addresses),
-        bind1st(AddressSelection(), family));
-}
-
-// Return one address matching the given family
-bool NameserverEntry::getAddress(NameserverAddress& address, short family)
-{
-    // Get the shared_ptr object that point to "this" object
-    shared_ptr<NameserverEntry> shared_ptr_to_this = shared_from_this();
-
-    if(family == AF_INET){
-        if(v4_addresses_.size() == 0) return false;
-
-        address = NameserverAddress(shared_ptr_to_this, v4_address_selector_(), AF_INET);
-        return true;
-    } else if(family == AF_INET6){
-        if(v6_addresses_.size() == 0) return false;
-
-        //address = NameserverAddress(shared_from_this(), v6_address_selector_(), AF_INET6);
-        return true;
+    if (getState() == EXPIRED && expired_ok) {
+        return READY;
     }
-    return false;
+    return getState();
 }
 
 // Return the address corresponding to the family
-asiolink::IOAddress NameserverEntry::getAddressAtIndex(uint32_t index, short family) const
-{
-    const vector<AddressEntry> *addresses = &v4_addresses_;
-    if(family == AF_INET6){
-        addresses = &v6_addresses_;
-    }
-    assert(index < addresses->size());
+asiolink::IOAddress
+NameserverEntry::getAddressAtIndex(size_t index, AddressFamily family) const {
+    Lock lock(mutex_);
 
-    return (*addresses)[index].getAddress();
+    assert(index < addresses_[family].size());
+
+    return (addresses_[family][index].getAddress());
 }
 
 // Set the address RTT to a specific value
-void NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
+void
+NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
+    Lock lock(mutex_);
 
     // Search through the list of addresses for a match
-    for (AddressVectorIterator i = v4_addresses_.begin(); i != v4_addresses_.end(); ++i) {
-        if (i->getAddress().equal(address)) {
-            i->setRTT(rtt);
-
-            // Update the selector
-            updateAddressSelector(v4_addresses_, v4_address_selector_);
-            return;
+    AddressFamily family(V4_ONLY);
+    for (;;) {
+        BOOST_FOREACH(AddressEntry& entry, addresses_[family]) {
+            if (entry.getAddress().equal(address)) {
+                entry.setRTT(rtt);
+                return;
+            }
         }
-    }
 
-    // Search the v6 list
-    for (AddressVectorIterator i = v6_addresses_.begin(); i != v6_addresses_.end(); ++i) {
-        if (i->getAddress().equal(address)) {
-            i->setRTT(rtt);
-
-            // Update the selector
-            updateAddressSelector(v6_addresses_, v6_address_selector_);
-            return;
+        // Hack. C++ does not allow ++ on enums, enumerating trough them is pain
+        switch (family) {
+            case V4_ONLY: family = V6_ONLY; break;
+            default: return;
         }
     }
 }
 
-// Update the address's rtt 
+// Update the address's rtt
 #define UPDATE_RTT_ALPHA 0.7
-void NameserverEntry::updateAddressRTTAtIndex(uint32_t rtt, uint32_t index, short family) {
-    vector<AddressEntry>* addresses = &v4_addresses_;
-    if(family == AF_INET6){
-        addresses = &v6_addresses_;
-    }
+void
+NameserverEntry::updateAddressRTTAtIndex(uint32_t rtt, size_t index,
+    AddressFamily family)
+{
+    Lock lock(mutex_);
 
     //make sure it is a valid index
-    if(index >= addresses->size()) return;
+    if(index >= addresses_[family].size()) return;
 
     // Smoothly update the rtt
     // The algorithm is as the same as bind8/bind9:
     //    new_rtt = old_rtt * alpha + new_rtt * (1 - alpha), where alpha is a float number in [0, 1.0]
     // The default value for alpha is 0.7
-    uint32_t old_rtt = (*addresses)[index].getRTT();
-    uint32_t new_rtt = (int)(old_rtt * UPDATE_RTT_ALPHA + rtt * (1 - UPDATE_RTT_ALPHA));
-    (*addresses)[index].setRTT(new_rtt);
-
-    // Update the selector
-    if(family == AF_INET) { 
-        updateAddressSelector(v4_addresses_, v4_address_selector_);
-    } else if(family == AF_INET6) {
-        updateAddressSelector(v6_addresses_, v6_address_selector_);
+    uint32_t old_rtt = addresses_[family][index].getRTT();
+    uint32_t new_rtt = (uint32_t)(old_rtt * UPDATE_RTT_ALPHA + rtt *
+        (1 - UPDATE_RTT_ALPHA));
+    addresses_[family][index].setRTT(new_rtt);
+}
+
+void
+NameserverEntry::updateAddressRTT(uint32_t rtt,
+    const asiolink::IOAddress& address, AddressFamily family)
+{
+    Lock lock(mutex_);
+    for (size_t i(0); i < addresses_[family].size(); ++ i) {
+        if (addresses_[family][i].getAddress().equal(address)) {
+            updateAddressRTTAtIndex(rtt, i, family);
+            return;
+        }
     }
 }
 
 // Sets the address to be unreachable
-void NameserverEntry::setAddressUnreachable(const IOAddress& address) {
+void
+NameserverEntry::setAddressUnreachable(const IOAddress& address) {
     setAddressRTT(address, AddressEntry::UNREACHABLE);
 }
 
-// Update the address selector according to the RTTs
-//
-// Each address has a probability to be selected if multiple addresses are available
-// The weight factor is equal to 1/(rtt*rtt), then all the weight factors are normalized
-// to make the sum equal to 1.0
-void NameserverEntry::updateAddressSelector(std::vector<AddressEntry>& addresses, 
-        WeightedRandomIntegerGenerator& selector)
-{
-    vector<double> probabilities;
-    for(vector<AddressEntry>::iterator it = addresses.begin(); 
-            it != addresses.end(); ++it){
-        uint32_t rtt = (*it).getRTT();
-        if(rtt == 0) {
-            isc_throw(RTTIsZero, "The RTT is 0");
+/**
+ * \short A callback into the resolver.
+ *
+ * Whenever we ask the resolver something, this is created and the answer is
+ * fed back trough this. It holds a shared pointer to the entry so it is not
+ * destroyed too soon.
+ */
+class NameserverEntry::ResolverCallback : public ResolverInterface::Callback {
+    public:
+        ResolverCallback(shared_ptr<NameserverEntry> entry,
+            AddressFamily family, const RRType& type) :
+            entry_(entry),
+            family_(family),
+            type_(type)
+        { }
+        /**
+         * \short We received the address successfully.
+         *
+         * This extracts the addresses out from the response and puts them
+         * inside the entry. It tries to reuse the address entries from before (if there were any), to keep their RTTs.
+         */
+        virtual void success(const shared_ptr<AbstractRRset>& response) {
+            time_t now = time(NULL);
+
+            Lock lock(entry_->mutex_);
+
+            vector<AddressEntry> entries;
+
+            if (response->getType() != type_ ||
+                response->getClass() != RRClass(entry_->getClass()))
+            {
+                // TODO Log we got answer of different type
+                failureInternal(lock);
+                return;
+            }
+
+            RdataIteratorPtr i(response->getRdataIterator());
+            // TODO Remove at merge with trunk
+            i->first();
+            while (! i->isLast()) {
+                // Try to find the original value and reuse it
+                string address(i->getCurrent().toText());
+                AddressEntry *found(NULL);
+                BOOST_FOREACH(AddressEntry& entry,
+                    entry_->previous_addresses_[family_])
+                {
+                    if (entry.getAddress().toText() == address) {
+                        // Good, found it.
+                        found = &entry;
+                        break;
+                    }
+                }
+                // If we found it, use it. If not, create a new one.
+                entries.push_back(found ? *found : AddressEntry(IOAddress(
+                    i->getCurrent().toText()), 1));
+                i->next();
+            }
+
+            // We no longer need the previous set of addresses, we have
+            // the current ones now.
+            entry_->previous_addresses_[family_].clear();
+
+            if (entries.empty()) {
+                // No data there, count it as a failure
+                failureInternal(lock);
+            } else {
+                // We received the data, so mark it
+                entry_->expect_address_[family_] = false;
+                entry_->expect_address_[ANY_OK] =
+                    entry_->expect_address_[V4_ONLY] ||
+                    entry_->expect_address_[V6_ONLY];
+                // Everything is here (all address families)
+                if (!entry_->expect_address_[ANY_OK]) {
+                    entry_->setState(READY);
+                }
+                // We have some address
+                entry_->has_address_[ANY_OK] =
+                    entry_->has_address_[family_] = true;
+                // Insert the entries inside
+                entry_->addresses_[family_].swap(entries);
+                // Update the expiration time. If it is 0, it means we
+                // did not set it yet, so reset
+                time_t expiration(now + response->getTTL().getValue());
+                if (entry_->expiration_) {
+                    // We expire at the time first address expires
+                    entry_->expiration_ = min(entry_->expiration_, expiration);
+                } else {
+                    // We have no expiration time set, use this one
+                    entry_->expiration_ = expiration;
+                }
+                // Run the right callbacks
+                dispatchCallbacks(lock);
+            }
         }
-
-        if(rtt == AddressEntry::UNREACHABLE) {
-            probabilities.push_back(0);
-        } else {
-            probabilities.push_back(1.0/(rtt*rtt));
+        /**
+         * \short The resolver failed to retrieve the data.
+         *
+         * So mark the current address family as unreachable.
+         */
+        virtual void failure() {
+            Lock lock(entry_->mutex_);
+            failureInternal(lock);
         }
-    }
-    // Calculate the sum
-    double sum = accumulate(probabilities.begin(), probabilities.end(), 0.0);
-
-    if(sum != 0) {
-        // Normalize the probabilities to make the sum equal to 1.0
-        for(vector<double>::iterator it = probabilities.begin(); 
-                it != probabilities.end(); ++it){
-            (*it) /= sum;
+    private:
+        shared_ptr<NameserverEntry> entry_;
+        AddressFamily family_;
+        RRType type_;
+
+        // Dispatches all relevant callbacks. Keeps lock unlocked afterwards.
+        // TODO: We might want to use recursive lock and get rid of this
+        void dispatchCallbacks(Lock& lock)
+        {
+            // We dispatch ANY addresses if there is at last one address or
+            // there's no chance we'll get some in future
+            bool dispatch_any = entry_->has_address_[ANY_OK] ||
+                !entry_->expect_address_[ANY_OK];
+            // Sort out the callbacks we want
+            vector<CallbackPair> keep;
+            vector<shared_ptr<NameserverEntry::Callback> > dispatch;
+            BOOST_FOREACH(const CallbackPair &callback, entry_->callbacks_)
+            {
+                if (callback.first == family_ || (dispatch_any &&
+                    callback.first == ANY_OK))
+                {
+                    dispatch.push_back(callback.second);
+                } else {
+                    keep.push_back(callback);
+                }
+            }
+            // Put there only the ones that we do not want, drop the rest
+            keep.swap(entry_->callbacks_);
+            keep.clear();
+
+            // We can't keep the lock while we execute callbacks
+            lock.unlock();
+            // Run all the callbacks
+            /*
+             * FIXME: This is not completely exception safe. If there's an
+             * exception in a callback, we lose the rest of them.
+             */
+            BOOST_FOREACH(const shared_ptr<NameserverEntry::Callback>&
+                callback, dispatch)
+            {
+                (*callback)(entry_);
+            }
         }
-    } else if(probabilities.size() > 0){
-        // If all the nameservers are unreachable, the sum will be 0
-        // So give each server equal opportunity to be selected.
-        for(vector<double>::iterator it = probabilities.begin(); 
-                it != probabilities.end(); ++it){
-            (*it) = 1.0/probabilities.size();
+
+        // Handle a failure to optain data. Dispatches callbacks and leaves
+        // lock unlocked
+        void failureInternal(Lock &lock) {
+            // Set state of the addresses
+            entry_->expect_address_[family_] = false;
+            entry_->expect_address_[ANY_OK] =
+                entry_->expect_address_[V4_ONLY] ||
+                entry_->expect_address_[V6_ONLY];
+            // When we do not expect any more addresses, decide the state
+            if (!entry_->expect_address_[ANY_OK]) {
+                if (entry_->has_address_[ANY_OK]) {
+                    // We have at last one kind of address, so OK
+                    entry_->setState(READY);
+                } else {
+                    // No addresses :-(
+                    entry_->setState(UNREACHABLE);
+                }
+            }
+            // Drop the previous addresses, no use of them now
+            entry_->previous_addresses_[family_].clear();
+            // Dispatch any relevant callbacks
+            dispatchCallbacks(lock);
         }
-    }
+};
+
+void
+NameserverEntry::askIP(shared_ptr<ResolverInterface> resolver,
+    const RRType& type, AddressFamily family)
+{
+    QuestionPtr question(new Question(Name(getName()), RRClass(getClass()),
+        type));
+    shared_ptr<ResolverCallback> callback(new ResolverCallback(
+        shared_from_this(), family, type));
+    resolver->resolve(question, callback);
+}
 
-    selector.reset(probabilities);
+void
+NameserverEntry::askIP(shared_ptr<ResolverInterface> resolver,
+    shared_ptr<Callback> callback, AddressFamily family)
+{
+    Lock lock(mutex_);
+
+    if (getState() == EXPIRED || getState() == NOT_ASKED) {
+        // We will request the addresses
+
+        // Set internal state first
+        // We store the old addresses so we can pick their RTT when
+        // we get the same addresses again (most probably)
+        previous_addresses_[V4_ONLY].clear();
+        previous_addresses_[V6_ONLY].clear();
+        addresses_[V4_ONLY].swap(previous_addresses_[V4_ONLY]);
+        addresses_[V6_ONLY].swap(previous_addresses_[V6_ONLY]);
+        setState(IN_PROGRESS);
+        has_address_[V4_ONLY] = has_address_[V6_ONLY] = has_address_[ANY_OK] =
+            false;
+        expect_address_[V4_ONLY] = expect_address_[V6_ONLY] =
+            expect_address_[ANY_OK] = true;
+        expiration_ = 0;
+
+        // Store the callback
+        callbacks_.push_back(CallbackPair(family, callback));
+
+        // Ask for both types of addresses
+        // We are unlocked here, as the callback from that might want to lock
+        lock.unlock();
+        askIP(resolver, RRType::A(), V4_ONLY);
+        askIP(resolver, RRType::AAAA(), V6_ONLY);
+        // Make sure we end the routine when we are not locked
+        return;
+    } else {
+        // We already asked. Do we expect this address type still to come?
+        if (!expect_address_[family]) {
+            // We do not expect it to come, dispatch right away
+            lock.unlock();
+            (*callback)(shared_from_this());
+            return;
+        } else {
+            // It will come in future, store the callback until then
+            callbacks_.push_back(CallbackPair(family, callback));
+        }
+    }
 }
 
 } // namespace dns

+ 131 - 94
src/lib/nsas/nameserver_entry.h

@@ -20,15 +20,21 @@
 #include <string>
 #include <vector>
 #include <boost/thread.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <exceptions/exceptions.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
 
 #include "address_entry.h"
 #include "asiolink.h"
-#include "exceptions/exceptions.h"
+#include "nsas_types.h"
 #include "hash_key.h"
 #include "lru_list.h"
+#include "fetchable.h"
+#include "resolver_interface.h"
 #include "nsas_entry.h"
-#include "random_number_generator.h"
-#include "rrset.h"
+#include "nameserver_address.h"
 
 namespace isc {
 namespace nsas {
@@ -67,7 +73,8 @@ public:
     {}
 };
 
-
+class ZoneEntry;
+class ResolverInterface;
 
 /// \brief Nameserver Entry
 ///
@@ -75,13 +82,6 @@ public:
 /// for several zones (hence is pointed to by more than one zone entry), and
 /// may have several addresses associated with it.
 ///
-/// When created, zero or more addresses may be given.  At any time, the list
-/// of addresses may be updated.  This may occur (a) after creation, either to
-/// to get the list of addresses when none have been supplied or to replace
-/// glue records, or (b) when the object has been accessed but found to be
-/// expired (the address records have reached their TTL).
-/// TODO: Add code for update of addresses
-///
 /// The addresses expire after their TTL has been reached.  For simplicity,
 /// (and because it is unlikely that A and AAAA records from the same zone have
 /// different TTLs) there is one expiration time for all address records.
@@ -90,68 +90,57 @@ public:
 ///
 /// As this object will be stored in the nameserver address store LRU list,
 /// it is derived from the LRU list entry class.
+///
+/// It uses shared_from_this in its methods. It must live inside a shared_ptr.
 
-class NameserverEntry : public NsasEntry<NameserverEntry>{
+class NameserverEntry : public NsasEntry<NameserverEntry>, public Fetchable {
 public:
     /// List of addresses associated with this nameserver
-    typedef std::vector<AddressEntry>   AddressVector;
+    typedef std::vector<NameserverAddress>   AddressVector;
     typedef AddressVector::iterator     AddressVectorIterator;
 
     /// \brief Constructor where no A records are supplied.
     ///
     /// \param name Name of the nameserver,
     /// \param class_code class of the nameserver
-    NameserverEntry(const std::string& name, uint16_t class_code) :
-        name_(name), classCode_(class_code)
-    {}
-
-    /// Constructor where one or more RRsets of A/AAAA records are supplied.
-    /// The class is taken from class of address records and the name from
-    /// the owner of the records.  If both sets of information are supplied
-    /// and the owner names are different, the V4 set wins out; the V6 set of
-    /// information is ignored and an error message is logged.
-    ///
-    /// \param v4Set RRset of A records
-    /// \param v6Set RRset of AAAA records
-    /// \param curtime Current time.  Present for testing, but also as a
-    /// possible optimisation if the caller has the current time (it saves
-    /// the overhead of a call to time()).  The default value of 0 requests
-    /// the constructor to get its own copy of the current time.
-    NameserverEntry(const isc::dns::AbstractRRset* v4Set,
-        const isc::dns::AbstractRRset* v6Set, time_t curtime = 0);
-
-    /// \brief Virtual Destructor
-    virtual ~NameserverEntry()
+    NameserverEntry(const std::string& name,
+        const isc::dns::RRClass& class_code) :
+        name_(name),
+        classCode_(class_code),
+        expiration_(0)
     {}
 
-    /// \brief Return Address
-    ///
-    /// Returns a vector of addresses corresponding to this nameserver.
-    /// It is up to the caller to 
-    ///
-    /// \param addresses Vector of address entries into which will be appended
-    /// addresses that match the specified criteria. (The reason for choosing
-    /// this signature is that addresses from more than one nameserver may be
-    /// retrieved, in which case appending to an existing list of addresses is
-    /// convenient.)
-    /// \param family Set to AF_INET/AF_INET6 for V6/V6 addresses, anything
-    /// else for all addresses.
-    virtual void getAddresses(NameserverEntry::AddressVector& addresses,
-        short family = 0) const;
-
-    /// \brief Return one address
-    ///
-    /// Return one address corresponding to this nameserver
-    /// \param address NameserverAddress object used to receive the address
-    /// \param family The family of user request, AF_INET or AF_INET6
-    /// \return true if one address is found, false otherwise
-    virtual bool getAddress(NameserverAddress& address, short family);
+    /*
+     * \brief Return Address
+     *
+     * Returns a vector of addresses corresponding to this nameserver.
+     *
+     * \param addresses Vector of address entries into which will be appended
+     *     addresses that match the specified criteria. (The reason for
+     *     choosing this signature is that addresses from more than one
+     *     nameserver may be retrieved, in which case appending to an existing
+     *     list of addresses is convenient.)
+     * \param family The family of address that is requested.
+     * \param expired_ok Return addresses even when expired. This is here
+     *     because an address with TTL 0 is expired at the exact time it
+     *     arrives. But when we call the callback, the owner of callback
+     *     is allowed to use them anyway so it should set expired_ok
+     *     to true.
+     * \return The state this is currently in. If the TTL expires, it enters
+     *     the EXPIRED state by itself and passes no addresses. It may be
+     *     IN_PROGRESS and still return some addresses (when one address family
+     *     arrived and is is returned, but the other is still on the way).
+     * \todo Should we sort out unreachable addresses as well?
+     */
+    Fetchable::State getAddresses(AddressVector& addresses,
+        AddressFamily family = ANY_OK, bool expired_ok = false);
 
     /// \brief Return Address that corresponding to the index
     ///
     /// \param index The address index in the address vector
-    /// \param family The address family, AF_INET or AF_INET6
-    virtual asiolink::IOAddress getAddressAtIndex(uint32_t index, short family) const;
+    /// \param family The address family, V4_ONLY or V6_ONLY
+    asiolink::IOAddress getAddressAtIndex(size_t index,
+        AddressFamily family) const;
 
     /// \brief Update RTT
     ///
@@ -159,29 +148,45 @@ public:
     ///
     /// \param address Address to update
     /// \param RTT New RTT for the address
-    virtual void setAddressRTT(const asiolink::IOAddress& address, uint32_t rtt);
+    void setAddressRTT(const asiolink::IOAddress& address, uint32_t rtt);
 
     /// \brief Update RTT of the address that corresponding to the index
     ///
+    /// Shouldn't probably be used directly. Use corresponding
+    /// NameserverAddress.
     /// \param rtt Round-Trip Time
     /// \param index The address's index in address vector
-    /// \param family The address family, AF_INET or AF_INET6
-    virtual void updateAddressRTTAtIndex(uint32_t rtt, uint32_t index, short family);
+    /// \param family The address family, V4_ONLY or V6_ONLY
+    void updateAddressRTTAtIndex(uint32_t rtt, size_t index,
+        AddressFamily family);
+    /**
+     * \short Update RTT of an address.
+     *
+     * This is similar to updateAddressRTTAtIndex, but you pass the address,
+     * not it's index. Passing the index might be unsafe, because the position
+     * of the address or the cound of addresses may change in time.
+     *
+     * \param rtt Round-Trip Time
+     * \param address The address whose RTT should be updated.
+     * \param family The address family, V4_ONLY or V6_ONLY
+     */
+    void updateAddressRTT(uint32_t rtt, const asiolink::IOAddress& address,
+        AddressFamily family);
 
     /// \brief Set Address Unreachable
     ///
     /// Sets the specified address to be unreachable
     ///
     /// \param address Address to update
-    virtual void setAddressUnreachable(const asiolink::IOAddress& address);
+    void setAddressUnreachable(const asiolink::IOAddress& address);
 
     /// \return Owner Name of RRset
-    virtual std::string getName() const {
+    std::string getName() const {
         return name_;
     }
 
     /// \return Class of RRset
-    virtual short getClass() const {
+    const isc::dns::RRClass& getClass() const {
         return classCode_;
     }
 
@@ -197,43 +202,75 @@ public:
     /// Returns the expiration time of addresses for this nameserver.  For
     /// simplicity, this quantity is calculated as the minimum expiration time
     /// of the A and AAAA address records.
-    virtual time_t getExpiration() const {
+    time_t getExpiration() const {
         return expiration_;
     }
 
-    /// \brief Predicate for Address Selection
-    ///
-    /// Returns false if the address family of a given entry matches the address
-    /// family given or if the address family is 0 (which means return all
-    /// addresses).  This curious logic is needed for use in the remove_copy_if
-    /// algorithm, which copies all values apart from those for which the
-    /// criteria is met.
-    class AddressSelection : public std::binary_function<short, AddressEntry, bool> {
-    public:
-        bool operator()(short family, const AddressEntry& entry) const {
-            bool match = (entry.getAddress().getFamily() == family) ||
-                (family == 0);
-            return (! match);
-        }
+    /// \name Obtaining the IP addresses from resolver
+    //@{
+    /// \short A callback that some information here arrived (or are unavailable).
+    struct Callback {
+        virtual void operator()(boost::shared_ptr<NameserverEntry> self) = 0;
+        /// \short Virtual destructor, so descendants are properly cleaned up
+        virtual ~ Callback() {}
     };
 
+    /**
+     * \short Asks the resolver for IP address (or addresses).
+     *
+     * Adds a callback for given zone when they are ready or the information
+     * is found unreachable.
+     *
+     * If it is not in NOT_ASKED or EXPIRED state, it does not ask the for the
+     * IP address again, it just inserts the callback. It is up to the caller
+     * not to insert one callback multiple times.
+     *
+     * The callback might be called directly from this function.
+     *
+     * \param resolver Who to ask.
+     * \param callback The callback.
+     * \param family Which addresses are interesting to the caller. This does
+     *     not change which adresses are requested, but the callback might
+     *     be executed when at last one requested type is available (eg. not
+     *     waiting for the other one).
+     * \return The state the entry is currently in. It can return UNREACHABLE
+     *     even when there are addresses, if there are no addresses for this
+     *     family.
+     */
+    void askIP(boost::shared_ptr<ResolverInterface> resolver,
+        boost::shared_ptr<Callback> callback, AddressFamily family);
+    //@}
+
 private:
-    /// \brief Update the address selector according to the RTTs of addresses
+    mutable boost::recursive_mutex    mutex_;     ///< Mutex protecting this object
+    std::string     name_;              ///< Canonical name of the nameserver
+    isc::dns::RRClass classCode_;       ///< Class of the nameserver
+    /**
+     * \short Address lists.
+     *
+     * Only V4_ONLY and V6_ONLY is used, therefore we use the nearest larger
+     * value as the size of the array.
+     *
+     * previous_addresses is kept until the data arrive again on re-fetch and
+     * is used to pick up the RTTs from there.
+     */
+    std::vector<AddressEntry> addresses_[ANY_OK], previous_addresses_[ANY_OK];
+    time_t          expiration_;        ///< Summary expiration time. 0 = unset
+    // Do we have some addresses already? Do we expect some to come?
+    // These are set after asking for IP, if NOT_ASKED, they are uninitialized
+    bool has_address_[ADDR_REQ_MAX], expect_address_[ADDR_REQ_MAX];
+    // Callbacks from resolver
+    class ResolverCallback;
+    friend class ResolverCallback;
+    // Callbacks inserted into this object
+    typedef std::pair<AddressFamily, boost::shared_ptr<Callback> >
+        CallbackPair;
+    std::vector<CallbackPair> callbacks_;
+    /// \short Private version that does the actual asking of one address type
     ///
-    /// \param addresses The address list
-    /// \param selector Weighted random generator
-    void updateAddressSelector(std::vector<AddressEntry>& addresses, 
-            WeightedRandomIntegerGenerator& selector);
-
-    boost::mutex    mutex_;                              ///< Mutex protecting this object
-    std::string     name_;                               ///< Canonical name of the nameserver
-    uint16_t        classCode_;                          ///< Class of the nameserver
-    std::vector<AddressEntry> v4_addresses_;             ///< Set of V4 addresses
-    std::vector<AddressEntry> v6_addresses_;             ///< Set of V6 addresses
-    time_t          expiration_;                         ///< Summary expiration time
-    time_t          last_access_;                        ///< Last access time to the structure
-    WeightedRandomIntegerGenerator v4_address_selector_; ///< Generate one integer according to different probability
-    WeightedRandomIntegerGenerator v6_address_selector_; ///< Generate one integer according to different probability
+    /// Call unlocked.
+    void askIP(boost::shared_ptr<ResolverInterface> resolver,
+        const isc::dns::RRType&, AddressFamily);
 };
 
 }   // namespace dns

+ 1 - 1
src/lib/nsas/nsas_entry.h

@@ -20,7 +20,7 @@
 #include <boost/enable_shared_from_this.hpp>
 #include <iostream>
 
-#include "exceptions/exceptions.h"
+#include <exceptions/exceptions.h>
 
 #include "hash_key.h"
 #include "hash_table.h"

+ 23 - 6
src/lib/nsas/nsas_types.h

@@ -17,16 +17,33 @@
 #ifndef __NSAS_TYPES_H
 #define __NSAS_TYPES_H
 
-#include <vector>
-#include <asio.h>
-
 /// \file nsas_types.h
 /// \brief Nameserver Address Store Types
 ///
-/// Defines a set of typedefs used within the Network Address Store.
+/// Defines a set of types used within the Network Address Store.
+
+namespace isc {
+namespace nsas {
 
-/// \brief Array of nameserver addresses
-typedef std::vector<ip::address>    NsasAddress
+/**
+ * \brief Address requested
+ *
+ * The order is significant, it is used as array indices and sometime only
+ * the first two are used.
+ */
+enum AddressFamily {
+    /// \short Interested only in IPv4 address
+    V4_ONLY,
+    /// \short Interested only in IPv6 address
+    V6_ONLY,
+    /// \short Any address is good
+    ANY_OK,
+    /// \short Bumper value, does not mean anything, it just represents the
+    /// max value
+    ADDR_REQ_MAX
+};
 
+}
+}
 
 #endif // __NSAS_TYPES_H

+ 7 - 14
src/lib/nsas/random_number_generator.h

@@ -60,7 +60,7 @@ private:
 /// \brief Weighted random integer generator
 ///
 /// Generate random integers according different probabilities
-class WeightedRandomIntegerGenerator{
+class WeightedRandomIntegerGenerator {
 public:
     /// \brief Constructor
     ///
@@ -71,7 +71,8 @@ public:
     /// other integers and the probability is proportional to its value.
     /// \param min The minimum integer that generated, other integers will be 
     /// min, min + 1, ..., min + probabilities.size() - 1
-    WeightedRandomIntegerGenerator(const std::vector<double>& probabilities, int min = 0):
+    WeightedRandomIntegerGenerator(const std::vector<double>& probabilities,
+        size_t min = 0):
         dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(min)
     {
         // The probabilities must be valid
@@ -82,7 +83,7 @@ public:
         // Init with the current time
         rng_.seed(time(NULL));
     }
-    
+
     /// \brief Default constructor
     ///
     WeightedRandomIntegerGenerator():
@@ -95,7 +96,7 @@ public:
     /// Change the weights of each integers
     /// \param probabilities The probabies for all the integers
     /// \param min The minimum integer that generated
-    void reset(const std::vector<double>& probabilities, int min = 0)
+    void reset(const std::vector<double>& probabilities, size_t min = 0)
     {
         // The probabilities must be valid
         assert(isProbabilitiesValid(probabilities));
@@ -109,23 +110,15 @@ public:
 
         // Reset the minimum integer
         min_ = min;
-
-        // Reset the random number generator
-        rng_.seed(time(NULL));
     }
 
     /// \brief Generate weighted random integer
-    int operator()()
+    size_t operator()()
     {
         return std::lower_bound(cumulative_.begin(), cumulative_.end(), uniform_real_gen_()) 
             - cumulative_.begin() + min_;
     }
 
-    /// \brief Destroctor
-    ~WeightedRandomIntegerGenerator()
-    {
-    }
-
 private:
     /// \brief Check the validation of probabilities vector
     ///
@@ -156,7 +149,7 @@ private:
     boost::mt19937 rng_;                        ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator 
     boost::uniform_real<> dist_;                ///< Uniformly distributed real numbers
     UniformRealGenerator uniform_real_gen_;     ///< Uniformly distributed random real numbers generator
-    int min_;                                   ///< The minimum integer that will be generated
+    size_t min_;                                   ///< The minimum integer that will be generated
 };
 
 }   // namespace dns

+ 80 - 0
src/lib/nsas/resolver_interface.h

@@ -0,0 +1,80 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $id$
+
+#ifndef __RESOLVER_INTERFACE_H
+#define __RESOLVER_INTERFACE_H
+
+#include <dns/message.h>
+#include <dns/rrset.h>
+
+/**
+ * \file resolver_interface.h
+ * \short Temporary interface to resolver.
+ *
+ * This file contains a dummy interface for the resolver, which does not yet
+ * exist. When the resolver appears, this file should either wrap its
+ * interface or, better, be removed completely.
+ */
+
+namespace isc {
+namespace nsas {
+
+/**
+ * \short Abstract interface to the resolver.
+ *
+ * Abstract interface to the resolver. The NameserverAddressStore uses this
+ * to ask for addresses. It is here because resolver does not yet exist.
+ *
+ * It is abstract to allow tests pass dummy resolvers.
+ */
+class ResolverInterface {
+    public:
+        /// \short An abstract callback when data from resolver are ready.
+        class Callback {
+            public:
+                /// \short Some data arrived.
+                virtual void success(
+                    const boost::shared_ptr<isc::dns::AbstractRRset>&
+                    response) = 0;
+                /**
+                 * \short No data available.
+                 *
+                 * \todo Pass some reason.
+                 */
+                virtual void failure() = 0;
+                /// \short Virtual destructor, so descendants are cleaned up
+                virtual ~ Callback() {};
+        };
+        typedef boost::shared_ptr<Callback> CallbackPtr;
+        /**
+         * \short Ask a question.
+         *
+         * Asks the resolver a question. Once the answer is ready
+         * the callback is called.
+         *
+         * \param question What to ask. The resolver will decide who.
+         * \param callback What should happen when the answer is ready.
+         */
+        virtual void resolve(const isc::dns::QuestionPtr& question,
+            const CallbackPtr& callback) = 0;
+        /// \short Virtual destructor, so descendants are properly cleaned up
+        virtual ~ ResolverInterface() {}
+};
+
+} // namespace nsas
+} // namespace isc
+
+#endif //__RESOLVER_INTERFACE_H

+ 2 - 1
src/lib/nsas/tests/Makefile.am

@@ -27,8 +27,9 @@ run_unittests_SOURCES += nameserver_address_unittest.cc
 run_unittests_SOURCES += nameserver_address_store_unittest.cc
 run_unittests_SOURCES += nameserver_entry_unittest.cc
 run_unittests_SOURCES += nsas_entry_compare_unittest.cc
-run_unittests_SOURCES += nsas_test_utilities.h 
+run_unittests_SOURCES += nsas_test.h
 run_unittests_SOURCES += zone_entry_unittest.cc
+run_unittests_SOURCES += fetchable_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 2 - 2
src/lib/nsas/tests/address_entry_unittest.cc

@@ -24,8 +24,8 @@
 #include <stdint.h>
 
 
-#include "asiolink.h"
-#include "address_entry.h"
+#include "../asiolink.h"
+#include "../address_entry.h"
 
 static std::string V4A_TEXT("1.2.3.4");
 static std::string V4B_TEXT("5.6.7.8");

+ 34 - 0
src/lib/nsas/tests/fetchable_unittest.cc

@@ -0,0 +1,34 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $id$
+
+#include "../fetchable.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc::nsas;
+
+namespace {
+
+TEST(Fetchable, accessMethods) {
+    Fetchable f;
+    EXPECT_EQ(Fetchable::NOT_ASKED, f.getState());
+    f.setState(Fetchable::IN_PROGRESS);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, f.getState());
+    Fetchable funr(Fetchable::UNREACHABLE);
+    EXPECT_EQ(Fetchable::UNREACHABLE, funr.getState());
+}
+
+}

+ 16 - 13
src/lib/nsas/tests/hash_deleter_unittest.cc

@@ -21,16 +21,19 @@
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
 
-#include "nsas_entry.h"
-#include "hash_table.h"
-#include "hash_key.h"
-#include "lru_list.h"
-#include "hash_deleter.h"
+#include <dns/rrclass.h>
+
+#include "../nsas_entry.h"
+#include "../hash_table.h"
+#include "../hash_key.h"
+#include "../lru_list.h"
+#include "../hash_deleter.h"
 
 #include "nsas_test.h"
-#include "nsas_entry_compare.h"
+#include "../nsas_entry_compare.h"
 
 using namespace std;
+using namespace isc::dns;
 
 namespace isc {
 namespace nsas {
@@ -40,13 +43,13 @@ namespace nsas {
 class HashDeleterTest : public ::testing::Test {
 protected:
     HashDeleterTest() :
-        entry1_(new TestEntry("alpha", 1)),
-        entry2_(new TestEntry("beta", 2)),
-        entry3_(new TestEntry("gamma", 3)),
-        entry4_(new TestEntry("delta", 4)),
-        entry5_(new TestEntry("epsilon", 5)),
-        entry6_(new TestEntry("zeta", 6)),
-        entry7_(new TestEntry("eta", 7)),
+        entry1_(new TestEntry("alpha", RRClass::IN())),
+        entry2_(new TestEntry("beta", RRClass::CH())),
+        entry3_(new TestEntry("gamma", RRClass::HS())),
+        entry4_(new TestEntry("delta", RRClass::IN())),
+        entry5_(new TestEntry("epsilon", RRClass::CH())),
+        entry6_(new TestEntry("zeta", RRClass::HS())),
+        entry7_(new TestEntry("eta", RRClass::IN())),
         hash_table_(new NsasEntryCompare<TestEntry>()),
         lru_list_(3, new HashDeleter<TestEntry>(hash_table_))
     {}

+ 26 - 16
src/lib/nsas/tests/hash_key_unittest.cc

@@ -21,9 +21,11 @@
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
 
-#include "hash_key.h"
+#include "../hash_key.h"
+#include <dns/rrclass.h>
 
 using namespace std;
+using namespace isc::dns;
 
 namespace isc {
 namespace nsas {
@@ -38,17 +40,17 @@ TEST_F(HashKeyTest, Constructor) {
     
     // Basic constructor
     string  test1("ABCDEF");
-    HashKey key1(test1.c_str(), test1.size(), 1);
+    HashKey key1(test1.c_str(), test1.size(), RRClass::IN());
     EXPECT_EQ(key1.key, test1.c_str());
     EXPECT_EQ(key1.keylen, test1.size());
-    EXPECT_EQ(key1.class_code, 1);
+    EXPECT_EQ(key1.class_code, RRClass::IN());
 
     // String constructor
     string  test2("uvwxyz");
-    HashKey key2(test2, 2);
+    HashKey key2(test2, RRClass::CH());
     EXPECT_EQ(key2.key, test2.c_str());
     EXPECT_EQ(key2.keylen, test2.size());
-    EXPECT_EQ(key2.class_code, 2);
+    EXPECT_EQ(key2.class_code, RRClass::CH());
 }
 
 // Equality check
@@ -59,17 +61,25 @@ TEST_F(HashKeyTest, Equality) {
     string  test4("ABCDE123");     // Different key (almost same)
     string  test5("uvwxyz987");    // Different key
 
-    EXPECT_TRUE(HashKey(test1, 1) == HashKey(test1, 1));   // Same key and class
-    EXPECT_FALSE(HashKey(test1, 1) == HashKey(test1, 2));  // Different class
-
-    EXPECT_TRUE(HashKey(test1, 2) == HashKey(test2, 2));   // Same value key/class
-    EXPECT_FALSE(HashKey(test1, 2) == HashKey(test2, 3));
-
-    EXPECT_TRUE(HashKey(test1, 3) == HashKey(test3, 3));   // Same key
-    EXPECT_FALSE(HashKey(test1, 3) == HashKey(test3, 4));
-
-    EXPECT_FALSE(HashKey(test1, 1) == HashKey(test4, 1));
-    EXPECT_FALSE(HashKey(test1, 1) == HashKey(test5, 1));
+    EXPECT_TRUE(HashKey(test1, RRClass::IN()) == HashKey(test1,
+        RRClass::IN()));   // Same key and class
+    EXPECT_FALSE(HashKey(test1, RRClass::IN()) == HashKey(test1,
+        RRClass::CH()));  // Different class
+
+    EXPECT_TRUE(HashKey(test1, RRClass::CH()) == HashKey(test2,
+        RRClass::CH()));   // Same value key/class
+    EXPECT_FALSE(HashKey(test1, RRClass::CH()) == HashKey(test2,
+        RRClass::IN()));
+
+    EXPECT_TRUE(HashKey(test1, RRClass::HS()) == HashKey(test3,
+        RRClass::HS()));   // Same key
+    EXPECT_FALSE(HashKey(test1, RRClass::HS()) == HashKey(test3,
+        RRClass::IN()));
+
+    EXPECT_FALSE(HashKey(test1, RRClass::IN()) == HashKey(test4,
+        RRClass::IN()));
+    EXPECT_FALSE(HashKey(test1, RRClass::IN()) == HashKey(test5,
+        RRClass::IN()));
 }
 
 } // namespace nsas

+ 36 - 7
src/lib/nsas/tests/hash_table_unittest.cc

@@ -16,17 +16,22 @@
 
 #include <gtest/gtest.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
 
 #include <string.h>
 #include <iostream>
 
-#include "hash_table.h"
-#include "hash_key.h"
+#include <dns/rrclass.h>
 
-#include "nsas_entry_compare.h"
+#include "../hash_table.h"
+#include "../hash_key.h"
+
+#include "../nsas_entry_compare.h"
 #include "nsas_test.h"
 
 using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns;
 
 namespace isc {
 namespace nsas {
@@ -44,10 +49,10 @@ protected:
     // Constructor - initialize the objects
     HashTableTest() :
         table_(new NsasEntryCompare<TestEntry>()),
-        dummy1_(new TestEntry("test", 1)),
-        dummy2_(new TestEntry("test", 1)),
-        dummy3_(new TestEntry("Something_Else", 1)),
-        dummy4_(new TestEntry("test", 3))
+        dummy1_(new TestEntry("test", RRClass::IN())),
+        dummy2_(new TestEntry("test", RRClass::IN())),
+        dummy3_(new TestEntry("Something_Else", RRClass::IN())),
+        dummy4_(new TestEntry("test", RRClass::CH()))
     {}
 
     // Members.
@@ -175,6 +180,30 @@ TEST_F(HashTableTest, GetTest) {
     EXPECT_TRUE(value.get() == NULL);
 }
 
+shared_ptr<TestEntry>
+pass(shared_ptr<TestEntry> value) {
+    return (value);
+}
+
+TEST_F(HashTableTest, GetOrAddTest) {
+    // Add one entry
+    EXPECT_TRUE(table_.add(dummy1_, dummy1_->hashKey()));
+
+    // Check it looks it up
+    std::pair<bool, shared_ptr<TestEntry> > result = table_.getOrAdd(
+        dummy1_->hashKey(), boost::bind(pass, dummy3_));
+    EXPECT_FALSE(result.first);
+    EXPECT_EQ(dummy1_.get(), result.second.get());
+
+    // Check it says it adds the value
+    result = table_.getOrAdd(dummy3_->hashKey(), boost::bind(pass, dummy3_));
+    EXPECT_TRUE(result.first);
+    EXPECT_EQ(dummy3_.get(), result.second.get());
+
+    // Check it really did add it
+    EXPECT_EQ(dummy3_.get(), table_.get(dummy3_->hashKey()).get());
+}
+
 // Test that objects with the same name and different classes are distinct.
 TEST_F(HashTableTest, ClassTest) {
 

+ 19 - 11
src/lib/nsas/tests/hash_unittest.cc

@@ -21,7 +21,7 @@
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
 
-#include "hash.h"
+#include "../hash.h"
 
 #include "nsas_test.h"
 
@@ -82,7 +82,8 @@ TEST_F(HashTest, Algorithm) {
     // Generate hash values
     for (int i = 0; i < 50; ++i) {
         string name = base + boost::lexical_cast<string>(i);
-        uint32_t hashval = hash(HashKey(name.c_str(), name.size(), 0));
+        uint32_t hashval = hash(HashKey(name.c_str(), name.size(),
+            RRClass(0)));
         EXPECT_LT(hashval, size);
         values.push_back(hashval);
     }
@@ -122,18 +123,22 @@ TEST_F(HashTest, MixedCase) {
     Hash hash(HASHTABLE_DEFAULT_SIZE, 255, false);    // Disable randomisation for testing
 
     // Case not ignored, hashes should be different
-    uint32_t value1 = hash(HashKey(test1.c_str(), test1.size(), 0), false);
-    uint32_t value2 = hash(HashKey(test2.c_str(), test2.size(), 0), false);
+    uint32_t value1 = hash(HashKey(test1.c_str(), test1.size(), RRClass::IN()),
+        false);
+    uint32_t value2 = hash(HashKey(test2.c_str(), test2.size(), RRClass::IN()),
+        false);
     EXPECT_NE(value1, value2);
 
     // Case ignored, hashes should be the same
-    uint32_t value3 = hash(HashKey(test1.c_str(), test1.size(), 0), true);
-    uint32_t value4 = hash(HashKey(test2.c_str(), test2.size(), 0), true);
+    uint32_t value3 = hash(HashKey(test1.c_str(), test1.size(), RRClass::IN()),
+        true);
+    uint32_t value4 = hash(HashKey(test2.c_str(), test2.size(), RRClass::IN()),
+        true);
     EXPECT_EQ(value3, value4);
 
     // Check the default setting.
-    uint32_t value5 = hash(HashKey(test1.c_str(), test1.size(), 0));
-    uint32_t value6 = hash(HashKey(test2.c_str(), test2.size(), 0));
+    uint32_t value5 = hash(HashKey(test1.c_str(), test1.size(), RRClass::IN()));
+    uint32_t value6 = hash(HashKey(test2.c_str(), test2.size(), RRClass::IN()));
     EXPECT_EQ(value5, value6);
 
     // ... and just for good measure
@@ -151,7 +156,8 @@ TEST_F(HashTest, ClassCodes) {
     // codes.
     vector<uint32_t> values;
     for (uint32_t i = 0; i < 10; ++i) {
-        values.push_back(hash(HashKey(test1.c_str(), test1.size(), i)));
+        values.push_back(hash(HashKey(test1.c_str(), test1.size(),
+            RRClass(i))));
     }
 
     // find the number of unique values in the array.  Although there can
@@ -177,8 +183,10 @@ TEST_F(HashTest, Overlong) {
     Hash hash(HASHTABLE_DEFAULT_SIZE, string1.size());
 
     // Do two hashes
-    uint32_t value1 = hash(HashKey(string1.c_str(), string1.size(), 0));
-    uint32_t value2 = hash(HashKey(string2.c_str(), string2.size(), 0));
+    uint32_t value1 = hash(HashKey(string1.c_str(), string1.size(),
+        RRClass(0)));
+    uint32_t value2 = hash(HashKey(string2.c_str(), string2.size(),
+        RRClass(0)));
     EXPECT_EQ(value1, value2);
 }
 

+ 18 - 18
src/lib/nsas/tests/lru_list_unittest.cc

@@ -22,8 +22,8 @@
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
 
-#include "nsas_entry.h"
-#include "lru_list.h"
+#include "../nsas_entry.h"
+#include "../lru_list.h"
 
 #include "nsas_test.h"
 
@@ -40,7 +40,7 @@ namespace nsas {
 class Dropped : public LruList<TestEntry>::Dropped {
 public:
     virtual void operator()(TestEntry* entry) const {
-        entry->setClass(entry->getClass() | 0x8000);
+        entry->setClass(RRClass(entry->getClass().getCode() | 0x8000));
     }
 };
 
@@ -49,13 +49,13 @@ public:
 class LruListTest : public ::testing::Test {
 protected:
     LruListTest() :
-        entry1_(new TestEntry("alpha", 1)),
-        entry2_(new TestEntry("beta", 2)),
-        entry3_(new TestEntry("gamma", 3)),
-        entry4_(new TestEntry("delta", 4)),
-        entry5_(new TestEntry("epsilon", 5)),
-        entry6_(new TestEntry("zeta", 6)),
-        entry7_(new TestEntry("eta", 7))
+        entry1_(new TestEntry("alpha", RRClass::IN())),
+        entry2_(new TestEntry("beta", RRClass::CH())),
+        entry3_(new TestEntry("gamma", RRClass::HS())),
+        entry4_(new TestEntry("delta", RRClass::IN())),
+        entry5_(new TestEntry("epsilon", RRClass::HS())),
+        entry6_(new TestEntry("zeta", RRClass::CH())),
+        entry7_(new TestEntry("eta", RRClass::IN()))
     {}
 
     virtual ~LruListTest() 
@@ -233,22 +233,22 @@ TEST_F(LruListTest, Dropped) {
     lru.add(entry2_);
     lru.add(entry3_);
 
-    EXPECT_EQ(1, entry1_->getClass());
-    EXPECT_EQ(2, entry2_->getClass());
+    EXPECT_EQ(RRClass::IN(), entry1_->getClass());
+    EXPECT_EQ(RRClass::CH(), entry2_->getClass());
 
     // Add another entry and check that the handler runs.
-    EXPECT_EQ(0, (entry1_->getClass() & 0x8000));
+    EXPECT_EQ(0, (entry1_->getClass().getCode() & 0x8000));
     lru.add(entry4_);
-    EXPECT_NE(0, (entry1_->getClass() & 0x8000));
+    EXPECT_NE(0, (entry1_->getClass().getCode() & 0x8000));
 
-    EXPECT_EQ(0, (entry2_->getClass() & 0x8000));
+    EXPECT_EQ(0, (entry2_->getClass().getCode() & 0x8000));
     lru.add(entry5_);
-    EXPECT_NE(0, (entry2_->getClass() & 0x8000));
+    EXPECT_NE(0, (entry2_->getClass().getCode() & 0x8000));
 
     // Delete an entry and check that the handler does not run. 
-    EXPECT_EQ(0, (entry3_->getClass() & 0x8000));
+    EXPECT_EQ(0, (entry3_->getClass().getCode() & 0x8000));
     lru.remove(entry3_);
-    EXPECT_EQ(0, (entry3_->getClass() & 0x8000));
+    EXPECT_EQ(0, (entry3_->getClass().getCode() & 0x8000));
 }
 
 // Miscellaneous tests - pathological conditions

+ 267 - 20
src/lib/nsas/tests/nameserver_address_store_unittest.cc

@@ -20,19 +20,29 @@
 /// address store.  These act to delete zones from the zone hash table when
 /// the element reaches the top of the LRU list.
 
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+
 #include <gtest/gtest.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
 
 #include <string.h>
-#include <vector>
+#include <cassert>
 
-#include "nameserver_address_store.h"
-#include "nsas_entry_compare.h"
-#include "nameserver_entry.h"
-#include "zone_entry.h"
+#include "../nameserver_address_store.h"
+#include "../nsas_entry_compare.h"
+#include "../nameserver_entry.h"
+#include "../zone_entry.h"
+#include "../address_request_callback.h"
 #include "nsas_test.h"
 
+using namespace isc::dns;
+using namespace std;
+using namespace boost;
+
 namespace isc {
 namespace nsas {
 
@@ -47,8 +57,10 @@ public:
     ///
     /// \param hashsize Size of the zone hash table
     /// \param lrusize Size of the zone hash table
-    DerivedNsas(uint32_t hashsize, uint32_t lrusize) :
-        NameserverAddressStore(hashsize, lrusize)
+    DerivedNsas(shared_ptr<TestResolver> resolver, uint32_t hashsize,
+        uint32_t lrusize) :
+        NameserverAddressStore(resolver, hashsize, lrusize),
+        resolver_(resolver)
     {}
 
     /// \brief Virtual Destructor
@@ -58,44 +70,109 @@ public:
     /// \brief Add Nameserver Entry to Hash and LRU Tables
     void AddNameserverEntry(boost::shared_ptr<NameserverEntry>& entry) {
         HashKey h = entry->hashKey();
-        nameserver_hash_.add(entry, h);
-        nameserver_lru_.add(entry);
+        nameserver_hash_->add(entry, h);
+        nameserver_lru_->add(entry);
     }
 
     /// \brief Add Zone Entry to Hash and LRU Tables
     void AddZoneEntry(boost::shared_ptr<ZoneEntry>& entry) {
         HashKey h = entry->hashKey();
-        zone_hash_.add(entry, h);
-        zone_lru_.add(entry);
+        zone_hash_->add(entry, h);
+        zone_lru_->add(entry);
+    }
+    /**
+     * \short Just wraps the common lookup
+     *
+     * It calls the lookup and provides the authority section
+     * if it is asked for by the resolver.
+     */
+    void lookupAndAnswer(const string& name, const RRClass& class_code,
+        shared_ptr<AbstractRRset> authority,
+        shared_ptr<AddressRequestCallback> callback)
+    {
+        size_t size(resolver_->requests.size());
+        NameserverAddressStore::lookup(name, class_code, callback, ANY_OK);
+        // It asked something, the only thing it can ask is the NS list
+        if (size < resolver_->requests.size()) {
+            resolver_->provideNS(size, authority);
+            // Once answered, drop the request so noone else sees it
+            resolver_->requests.erase(resolver_->requests.begin() + size);
+        } else {
+            ADD_FAILURE() << "Not asked for NS";
+        }
     }
+private:
+    shared_ptr<TestResolver> resolver_;
 };
 
 
 
 /// \brief Text Fixture Class
-class NameserverAddressStoreTest : public ::testing::Test {
+class NameserverAddressStoreTest : public TestWithRdata {
 protected:
 
-    // Constructor - initialize a set of nameserver and zone objects.  For convenience,
-    // these are stored in vectors.
-    NameserverAddressStoreTest()
+    NameserverAddressStoreTest() :
+        authority_(new RRset(Name("example.net."), RRClass::IN(), RRType::NS(),
+            RRTTL(128))),
+        empty_authority_(new RRset(Name("example.net."), RRClass::IN(),
+            RRType::NS(), RRTTL(128))),
+        resolver_(new TestResolver)
     {
+        // Constructor - initialize a set of nameserver and zone objects.  For
+        // convenience, these are stored in vectors.
         for (int i = 1; i <= 9; ++i) {
-            std::string name = "nameserver" + boost::lexical_cast<std::string>(i);
-            nameservers_.push_back(boost::shared_ptr<NameserverEntry>(new NameserverEntry(name, (40 + i))));
+            std::string name = "nameserver" + boost::lexical_cast<std::string>(
+                i);
+            nameservers_.push_back(boost::shared_ptr<NameserverEntry>(
+                new NameserverEntry(name, RRClass(40 + i))));
         }
 
+        // Some zones. They will not use the tables in this test, so it can be
+        // empty
         for (int i = 1; i <= 9; ++i) {
             std::string name = "zone" + boost::lexical_cast<std::string>(i);
-            zones_.push_back(boost::shared_ptr<ZoneEntry>(new ZoneEntry(name, (40 + i))));
+            zones_.push_back(boost::shared_ptr<ZoneEntry>(new ZoneEntry(
+                resolver_, name, RRClass(40 + i),
+                shared_ptr<HashTable<NameserverEntry> >(),
+                shared_ptr<LruList<NameserverEntry> >())));
         }
+
+        // A nameserver serving data
+        authority_->addRdata(ConstRdataPtr(new rdata::generic::NS(Name(
+            "ns.example.com."))));
+
+        // This is reused because of convenience, clear it just in case
+        NSASCallback::results.clear();
     }
 
     // Vector of pointers to nameserver and zone entries.
     std::vector<boost::shared_ptr<NameserverEntry> > nameservers_;
     std::vector<boost::shared_ptr<ZoneEntry> >       zones_;
+
+    RRsetPtr authority_, empty_authority_;
+
+    shared_ptr<TestResolver> resolver_;
+
+    class NSASCallback : public AddressRequestCallback {
+        public:
+            typedef pair<bool, NameserverAddress> Result;
+            static vector<Result> results;
+            virtual void success(const NameserverAddress& address) {
+                results.push_back(Result(true, address));
+            }
+            virtual void unreachable() {
+                results.push_back(Result(false, NameserverAddress()));
+            }
+    };
+
+    boost::shared_ptr<AddressRequestCallback> getCallback() {
+        return (boost::shared_ptr<AddressRequestCallback>(new NSASCallback));
+    }
 };
 
+vector<NameserverAddressStoreTest::NSASCallback::Result>
+    NameserverAddressStoreTest::NSASCallback::results;
+
 
 /// \brief Remove Zone Entry from Hash Table
 ///
@@ -105,7 +182,7 @@ TEST_F(NameserverAddressStoreTest, ZoneDeletionCheck) {
 
     // Create a NSAS with a hash size of three and a LRU size of 9 (both zone and
     // nameserver tables).
-    DerivedNsas nsas(2, 2);
+    DerivedNsas nsas(resolver_, 2, 2);
 
     // Add six entries to the tables.  After addition the reference count of each element
     // should be 3 - one for the entry in the zones_ vector, and one each for the entries
@@ -135,7 +212,7 @@ TEST_F(NameserverAddressStoreTest, NameserverDeletionCheck) {
 
     // Create a NSAS with a hash size of three and a LRU size of 9 (both zone and
     // nameserver tables).
-    DerivedNsas nsas(2, 2);
+    DerivedNsas nsas(resolver_, 2, 2);
 
     // Add six entries to the tables.  After addition the reference count of each element
     // should be 3 - one for the entry in the nameservers_ vector, and one each for the entries
@@ -156,5 +233,175 @@ TEST_F(NameserverAddressStoreTest, NameserverDeletionCheck) {
     EXPECT_EQ(1, nameservers_[1].use_count());
 }
 
+/**
+ * \short Try lookup on empty store.
+ *
+ * Check if it asks correct questions and it keeps correct internal state.
+ */
+TEST_F(NameserverAddressStoreTest, emptyLookup) {
+    DerivedNsas nsas(resolver_, 10, 10);
+    // Ask it a question
+    nsas.lookupAndAnswer("example.net.", RRClass::IN(), authority_,
+        getCallback());
+    // It should ask for IP addresses for ns.example.com.
+    EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), 0, 1));
+
+    // Ask another question for the same zone
+    nsas.lookup("example.net.", RRClass::IN(), getCallback());
+    // It should ask no more questions now
+    EXPECT_EQ(2, resolver_->requests.size());
+
+    // Ask another question with different zone but the same nameserver
+    authority_->setName(Name("example.com."));
+    nsas.lookupAndAnswer("example.com.", RRClass::IN(), authority_,
+        getCallback());
+    // It still should ask nothing
+    EXPECT_EQ(2, resolver_->requests.size());
+
+    // We provide IP address of one nameserver, it should generate all the
+    // results
+    EXPECT_NO_THROW(resolver_->answer(0, Name("ns.example.com."), RRType::A(),
+        rdata::in::A("192.0.2.1")));
+    EXPECT_EQ(3, NSASCallback::results.size());
+    BOOST_FOREACH(const NSASCallback::Result& result, NSASCallback::results) {
+        EXPECT_TRUE(result.first);
+        EXPECT_EQ("192.0.2.1", result.second.getAddress().toText());
+    }
+}
+
+/**
+ * \short Try looking up a zone that does not have any nameservers.
+ *
+ * It should not ask anything and say it is unreachable right away.
+ */
+TEST_F(NameserverAddressStoreTest, zoneWithoutNameservers) {
+    DerivedNsas nsas(resolver_, 10, 10);
+    // Ask it a question
+    nsas.lookupAndAnswer("example.net.", RRClass::IN(), empty_authority_,
+        getCallback());
+    // There should be no questions, because there's nothing to ask
+    EXPECT_EQ(0, resolver_->requests.size());
+    // And there should be one "unreachable" answer for the query
+    ASSERT_EQ(1, NSASCallback::results.size());
+    EXPECT_FALSE(NSASCallback::results[0].first);
+}
+
+/**
+ * \short Try looking up a zone that has only an unreachable nameserver.
+ *
+ * It should be unreachable. Furthermore, subsequent questions for that zone
+ * or other zone with the same nameserver should be unreachable right away,
+ * without further asking.
+ */
+TEST_F(NameserverAddressStoreTest, unreachableNS) {
+    DerivedNsas nsas(resolver_, 10, 10);
+    // Ask it a question
+    nsas.lookupAndAnswer("example.net.", RRClass::IN(), authority_,
+        getCallback());
+    // It should ask for IP addresses for example.com.
+    EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), 0, 1));
+
+    // Ask another question with different zone but the same nameserver
+    authority_->setName(Name("example.com."));
+    nsas.lookupAndAnswer("example.com.", RRClass::IN(), authority_,
+        getCallback());
+    // It should ask nothing more now
+    EXPECT_EQ(2, resolver_->requests.size());
+
+    // We say there are no addresses
+    resolver_->requests[0].second->failure();
+    resolver_->requests[1].second->failure();
+
+    // We should have 2 answers now
+    EXPECT_EQ(2, NSASCallback::results.size());
+    // When we ask one same and one other zone with the same nameserver,
+    // it should generate no questions and answer right away
+    nsas.lookup("example.net.", RRClass::IN(), getCallback());
+    authority_->setName(Name("example.org."));
+    nsas.lookupAndAnswer("example.org.", RRClass::IN(), authority_,
+        getCallback());
+    // There should be 4 negative answers now
+    EXPECT_EQ(4, NSASCallback::results.size());
+    BOOST_FOREACH(const NSASCallback::Result& result, NSASCallback::results) {
+        EXPECT_FALSE(result.first);
+    }
+}
+
+/**
+ * \short Try to stress it little bit by having multiple zones and nameservers.
+ *
+ * Does some asking, on a set of zones that share some nameservers, with
+ * slower answering, evicting data, etc.
+ */
+TEST_F(NameserverAddressStoreTest, CombinedTest) {
+    // Create small caches, so we get some evictions
+    DerivedNsas nsas(resolver_, 1, 1);
+    // Ask for example.net. It has single nameserver out of the zone
+    nsas.lookupAndAnswer("example.net.", RRClass::IN(), authority_,
+        getCallback());
+    // It should ask for the nameserver IP addresses
+    EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), 0, 1));
+    EXPECT_EQ(0, NSASCallback::results.size());
+    // But we do not answer it right away. We create a new zone and
+    // let this nameserver entry get out.
+    rrns_->addRdata(rdata::generic::NS("example.cz"));
+    nsas.lookupAndAnswer(EXAMPLE_CO_UK, RRClass::IN(), rrns_, getCallback());
+    // It really should ask something, one of the nameservers
+    // (or both)
+    ASSERT_GT(resolver_->requests.size(), 2);
+    Name name(resolver_->requests[2].first->getName());
+    EXPECT_TRUE(name == Name("example.fr") || name == Name("example.de") ||
+        name == Name("example.cz"));
+    EXPECT_NO_THROW(resolver_->asksIPs(name, 2, 3));
+    EXPECT_EQ(0, NSASCallback::results.size());
+
+    size_t request_count(resolver_->requests.size());
+    // This should still be in the hash table, so try it asks no more questions
+    nsas.lookup("example.net.", RRClass::IN(), getCallback());
+    EXPECT_EQ(request_count, resolver_->requests.size());
+    EXPECT_EQ(0, NSASCallback::results.size());
+
+    // We respond to one of the 3 nameservers
+    EXPECT_NO_THROW(resolver_->answer(2, name, RRType::A(),
+        rdata::in::A("192.0.2.1")));
+    // That should trigger one answer
+    EXPECT_EQ(1, NSASCallback::results.size());
+    EXPECT_TRUE(NSASCallback::results[0].first);
+    EXPECT_EQ("192.0.2.1",
+        NSASCallback::results[0].second.getAddress().toText());
+    EXPECT_NO_THROW(resolver_->answer(3, name, RRType::AAAA(),
+        rdata::in::AAAA("2001:bd8::1")));
+    // And there should be yet another query
+    ASSERT_GT(resolver_->requests.size(), 4);
+    EXPECT_NE(name, resolver_->requests[4].first->getName());
+    Name another_name = resolver_->requests[4].first->getName();
+    EXPECT_TRUE(another_name == Name("example.fr") ||
+        another_name == Name("example.de") ||
+        another_name == Name("example.cz"));
+    request_count = resolver_->requests.size();
+
+    // But when ask for a different zone with the first nameserver, it should
+    // ask again, as it is evicted already
+    authority_->setName(Name("example.com."));
+    nsas.lookupAndAnswer("example.com.", RRClass::IN(), authority_,
+        getCallback());
+    EXPECT_EQ(request_count + 2, resolver_->requests.size());
+    EXPECT_NO_THROW(resolver_->asksIPs(Name("ns.example.com."), request_count,
+        request_count + 1));
+    // Now, we answer both queries for the same address
+    // and three (one for the original, one for this one) more answers should
+    // arrive
+    NSASCallback::results.clear();
+    EXPECT_NO_THROW(resolver_->answer(0, Name("ns.example.com."), RRType::A(),
+        rdata::in::A("192.0.2.2")));
+    EXPECT_NO_THROW(resolver_->answer(request_count, Name("ns.example.com."),
+        RRType::A(), rdata::in::A("192.0.2.2")));
+    EXPECT_EQ(3, NSASCallback::results.size());
+    BOOST_FOREACH(const NSASCallback::Result& result, NSASCallback::results) {
+        EXPECT_TRUE(result.first);
+        EXPECT_EQ("192.0.2.2", result.second.getAddress().toText());
+    }
+}
+
 } // namespace nsas
 } // namespace isc

+ 38 - 26
src/lib/nsas/tests/nameserver_address_unittest.cc

@@ -16,13 +16,14 @@
 
 #include <gtest/gtest.h>
 
-#include "name.h"
-#include "nameserver_address.h"
-#include "rdata.h"
-#include "rrclass.h"
-#include "rrset.h"
-#include "rrttl.h"
-
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+
+#include "../nameserver_address.h"
+#include "../nameserver_entry.h"
 #include "nsas_test.h"
 
 namespace isc {
@@ -30,6 +31,7 @@ namespace nsas {
 
 using namespace dns;
 using namespace rdata;
+using namespace boost;
 
 #define TEST_ADDRESS_INDEX 1
 
@@ -37,35 +39,48 @@ using namespace rdata;
 class NameserverEntrySample {
 public:
     NameserverEntrySample():
-        rrv4_(Name("example.org"), RRClass::IN(), RRType::A(), RRTTL(1200))
+        name_("example.org"),
+        rrv4_(new BasicRRset(name_, RRClass::IN(), RRType::A(), RRTTL(1200)))
     {
         // Add some sample A records
-        rrv4_.addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
-        rrv4_.addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
-        rrv4_.addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
-
-        ns_.reset(new NameserverEntry(&rrv4_, NULL));
+        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
+        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
+        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
+
+        ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
+        shared_ptr<TestResolver> resolver(new TestResolver);
+        ns_->askIP(resolver, shared_ptr<Callback>(new Callback), ANY_OK);
+        resolver->asksIPs(name_, 0, 1);
+        resolver->requests[0].second->success(rrv4_);
     }
 
     // Return the sample NameserverEntry
     boost::shared_ptr<NameserverEntry>& getNameserverEntry() { return ns_; }
 
     // Return the IOAddress corresponding to the index in rrv4_
-    asiolink::IOAddress getAddressAtIndex(uint32_t index) { return ns_.get()->getAddressAtIndex(index, AF_INET); }
+    asiolink::IOAddress getAddressAtIndex(uint32_t index) {
+        return ns_.get()->getAddressAtIndex(index, V4_ONLY);
+    }
 
     // Return the addresses count stored in RRset
-    unsigned int getAddressesCount() const { return rrv4_.getRdataCount(); }
+    unsigned int getAddressesCount() const { return rrv4_->getRdataCount(); }
 
     // Return the RTT of the address
     uint32_t getAddressRTTAtIndex(uint32_t index) { 
         NameserverEntry::AddressVector addresses;
         ns_.get()->getAddresses(addresses);
-        return addresses[index].getRTT();
+        return (addresses[index].getAddressEntry().getRTT());
     }
 
 private:
-    BasicRRset rrv4_;                       ///< Standard RRSet - IN, A, lowercase name
+    Name name_;                             ///< Name of the sample
+    shared_ptr<BasicRRset> rrv4_;           ///< Standard RRSet - IN, A, lowercase name
     boost::shared_ptr<NameserverEntry> ns_; ///< Shared_ptr that points to a NameserverEntry object
+
+    class Callback : public NameserverEntry::Callback {
+        public:
+            virtual void operator()(shared_ptr<NameserverEntry>) { }
+    };
 };
 
 /// \brief Test Fixture Class
@@ -73,29 +88,26 @@ class NameserverAddressTest : public ::testing::Test {
 protected:
     // Constructor
     NameserverAddressTest(): 
-        ns_address_(ns_sample_.getNameserverEntry(), TEST_ADDRESS_INDEX, AF_INET),
-        invalid_ns_address_(ns_sample_.getNameserverEntry(), ns_sample_.getAddressesCount(), AF_INET)
+        ns_address_(ns_sample_.getNameserverEntry(),
+            ns_sample_.getNameserverEntry()->getAddressAtIndex(
+            TEST_ADDRESS_INDEX, V4_ONLY), V4_ONLY)
     {
     }
 
     NameserverEntrySample ns_sample_;
     // Valid NameserverAddress object
     NameserverAddress ns_address_;
-
-    // NameserverAddress object that constructed with invalid index
-    NameserverAddress invalid_ns_address_;
 };
 
 // Test that the address is equal to the address in NameserverEntry
 TEST_F(NameserverAddressTest, Address) {
     EXPECT_TRUE(ns_address_.getAddress().equal( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
 
-    // It will trigger an assert with the invalid index
-    ASSERT_DEATH(invalid_ns_address_.getAddress(), "");
-
     boost::shared_ptr<NameserverEntry> empty_ne((NameserverEntry*)NULL);
     // It will throw an NullNameserverEntryPointer exception with the empty NameserverEntry shared pointer
-    ASSERT_THROW({NameserverAddress empty_ns_address(empty_ne, 0, AF_INET);}, NullNameserverEntryPointer);
+    ASSERT_THROW({NameserverAddress empty_ns_address(empty_ne,
+        asiolink::IOAddress("127.0.0.1"), V4_ONLY);},
+        NullNameserverEntryPointer);
 }
 
 // Test that the RTT is updated

+ 347 - 394
src/lib/nsas/tests/nameserver_entry_unittest.cc

@@ -16,178 +16,93 @@
 
 #include <iostream>
 #include <algorithm>
-#include <cmath>
 
 #include <limits.h>
 #include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
 #include <gtest/gtest.h>
 
-#include "rdata.h"
-#include "rrset.h"
-#include "rrclass.h"
-#include "rrttl.h"
-#include "name.h"
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+#include <dns/name.h>
+#include <exceptions/exceptions.h>
 
-#include "asiolink.h"
-#include "address_entry.h"
-#include "nameserver_address.h"
-#include "nameserver_entry.h"
+#include "../asiolink.h"
+#include "../address_entry.h"
+#include "../nameserver_entry.h"
+#include "../nameserver_address.h"
+#include "../zone_entry.h"
 
 #include "nsas_test.h"
 
+using namespace isc::nsas;
 using namespace asiolink;
 using namespace std;
 using namespace isc::dns;
 using namespace rdata;
+using namespace boost;
 
-namespace isc {
-namespace nsas {
-
-// String constants.  These should end in a dot.
-static const std::string EXAMPLE_CO_UK("example.co.uk.");
-static const std::string EXAMPLE_NET("example.net.");
-static const std::string MIXED_EXAMPLE_CO_UK("EXAmple.co.uk.");
+namespace {
 
 /// \brief Test Fixture Class
-class NameserverEntryTest : public ::testing::Test {
+class NameserverEntryTest : public TestWithRdata {
 protected:
-
-    /// \brief Constructor
-    ///
-    /// Initializes the RRsets used in the tests.  The RRsets themselves have to
-    /// be initialized with the basic data on their construction. The Rdata for
-    /// them is added in SetUp().
-    NameserverEntryTest() :
-        rrv4_(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::A(), RRTTL(1200)),
-        rrcase_(Name(MIXED_EXAMPLE_CO_UK), RRClass::IN(), RRType::A(),
-            RRTTL(1200)),
-        rrch_(Name(EXAMPLE_CO_UK), RRClass::CH(), RRType::A(), RRTTL(1200)),
-        rrns_(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::NS(), RRTTL(1200)),
-        rrv6_(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::AAAA(), RRTTL(900)),
-        rrnet_(Name(EXAMPLE_NET), RRClass::IN(), RRType::A(), RRTTL(600))
-    {}
-
-    /// \brief Add Rdata to RRsets
-    ///
-    /// The data are added as const pointers to avoid the stricter type checking
-    /// applied by the Rdata code.  There is no need for it in these tests.
-    virtual void SetUp() {
-
-        // A records
-        rrv4_.addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
-        rrv4_.addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
-        rrv4_.addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
-
-        // A records
-        rrcase_.addRdata(ConstRdataPtr(new RdataTest<A>("13.14.15.16")));
-
-        // No idea what Chaosnet address look like other than they are 16 bits
-        // The fact that they are type A is probably also incorrect.
-        rrch_.addRdata(ConstRdataPtr(new RdataTest<A>("1324")));
-
-        // NS records take a single name
-        rrns_.addRdata(ConstRdataPtr(new RdataTest<NS>("example.fr")));
-        rrns_.addRdata(ConstRdataPtr(new RdataTest<NS>("example.de")));
-
-        // AAAA records
-        rrv6_.addRdata(ConstRdataPtr(new RdataTest<AAAA>("2001::1002")));
-        rrv6_.addRdata(ConstRdataPtr(new RdataTest<AAAA>("dead:beef:feed::")));
-
-        // A record for example.net
-        rrnet_.addRdata(ConstRdataPtr(new RdataTest<A>("17.18.18.20")));
-    }
-
-    /// \brief Data for the tests
-    BasicRRset rrv4_;           ///< Standard RRSet - IN, A, lowercase name
-    BasicRRset rrcase_;         ///< Mixed-case name
-    BasicRRset rrch_;           ///< Non-IN RRset (Chaos in this case)
-    BasicRRset rrns_;           ///< NS RRset
-    BasicRRset rrv6_;           ///< Standard RRset, IN, AAAA, lowercase name
-    BasicRRset rrnet_;          ///< example.net A RRset
-};
-
-/// \brief Compare Vectors of String
-///
-/// Compares two vectors of strings.  A GoogleTest check is done on the results.
-///
-/// \param vec1 First vector.  This may be reordered in the comparison.
-/// \param vec2 Second vector.  This may be reordered in the comparison
-static void CompareStringVectors(vector<string>& vec1, vector<string>& vec2)
-{
-    // Check that the vectors are the same size.
-    EXPECT_EQ(vec1.size(), vec2.size());
-
-    // Get into canonical order
-    sort(vec1.begin(), vec1.end());
-    sort(vec2.begin(), vec2.end());
-
-    // ... and look for a mismatch.
-    EXPECT_TRUE(equal(vec1.begin(), vec1.end(), vec2.begin()));
-}
-
-/// \brief Compare Ranges of Addresses
-///
-/// Compares the addresses held in an address vector with those held in the
-/// RRset from which it was dervived and checks that there is a 1:1
-/// mapping between the two.
-///
-/// \param av AddressVector retrieved from NameserverEntry object
-/// \param rrs BasicRRSet from which the vector was created
-static void CompareAddresses(NameserverEntry::AddressVector& av,
-    BasicRRset& rrs)
-{
-
-    // Extract addresses from address vector into strings
-    vector<string> avs;
-    BOOST_FOREACH(AddressEntry addr, av) {
-        avs.push_back(addr.getAddress().toText());
-    }
-
-    // Do the same for the Basic RRset
-    vector<string> rrstr;
-    RdataIteratorPtr i = rrs.getRdataIterator();
-    i->first();
-    while (! i->isLast()) {
-        rrstr.push_back(i->getCurrent().toText());
-        i->next();
-    }
-
-    // ... and compare the results
-    CompareStringVectors(avs, rrstr);
-}
-
-
-/// \brief Compare Address Vectors
-///
-/// Compares two address vectors by converting the addresses to string form
-/// and comparing the strings.  Any mismatch will be reported.
-///
-/// \param vec1 First address vector
-/// \param vec2 Second address vector
-static void CompareAddressVectors(NameserverEntry::AddressVector& vec1,
-    NameserverEntry::AddressVector& vec2) {
-
-    // Extract addresses from address vectors into strings
-    vector<string> strvec1;
-    BOOST_FOREACH(AddressEntry addr, vec1) {
-        strvec1.push_back(addr.getAddress().toText());
+    /// \short Just a really stupid callback counting times called
+    struct Callback : public NameserverEntry::Callback {
+        size_t count;
+        virtual void operator()(shared_ptr<NameserverEntry>) {
+            count ++;
+        }
+        Callback() : count(0) { }
+    };
+private:
+    /**
+     * \short Fills an rrset into the NameserverEntry trough resolver.
+     *
+     * This function is used when we want to pass data to a NameserverEntry
+     * trough the resolver.
+     * \param resolver The resolver used by the NameserverEntry
+     * \param index Index of the query in the resolver.
+     * \param set The answer. If the pointer is empty, it is taken
+     *     as a failure.
+     */
+    void fillSet(shared_ptr<TestResolver> resolver, size_t index,
+        shared_ptr<BasicRRset> set)
+    {
+        if (set) {
+            resolver->requests[index].second->success(set);
+        } else {
+            resolver->requests[index].second->failure();
+        }
     }
-
-    vector<string> strvec2;
-    BOOST_FOREACH(AddressEntry addr, vec2) {
-        strvec2.push_back(addr.getAddress().toText());
+protected:
+    /// Fills the nameserver entry with data trough ask IP
+    void fillNSEntry(shared_ptr<NameserverEntry> entry,
+        shared_ptr<BasicRRset> rrv4, shared_ptr<BasicRRset> rrv6)
+    {
+        // Prepare data to run askIP
+        shared_ptr<TestResolver> resolver(new TestResolver);
+        shared_ptr<Callback> callback(new Callback);
+        // Let it ask for data
+        entry->askIP(resolver, callback, ANY_OK);
+        // Check it really asked and sort the queries
+        EXPECT_TRUE(resolver->asksIPs(Name(entry->getName()), 0, 1));
+        // Respond with answers
+        fillSet(resolver, 0, rrv4);
+        fillSet(resolver, 1, rrv6);
     }
-
-    CompareStringVectors(strvec1, strvec2);
-}
+};
 
 /// Tests of the default constructor
 TEST_F(NameserverEntryTest, DefaultConstructor) {
 
     // Default constructor should not create any RRsets
-    NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN().getCode());
+    NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN());
     EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
-    EXPECT_EQ(RRClass::IN().getCode(), alpha.getClass());
+    EXPECT_EQ(RRClass::IN(), alpha.getClass());
 
     // Also check that no addresses have been created.
     NameserverEntry::AddressVector addresses;
@@ -195,118 +110,53 @@ TEST_F(NameserverEntryTest, DefaultConstructor) {
     EXPECT_TRUE(addresses.empty());
 }
 
+// Test the the RTT on tthe created addresses is not 0 and is different
+TEST_F(NameserverEntryTest, InitialRTT) {
 
-/// Tests of constructor passed a list of addresses.
-TEST_F(NameserverEntryTest, AddressListConstructor) {
-
-    // Initialize with no addresses and check that data returned has size of
-    // zero.
-    NameserverEntry alpha(NULL, NULL);
-    NameserverEntry::AddressVector av;
-    alpha.getAddresses(av);
-    EXPECT_EQ(0, av.size());
-
-    NameserverEntry::AddressVector av4;
-    alpha.getAddresses(av4, AF_INET);
-    EXPECT_EQ(0, av4.size());
-
-    NameserverEntry::AddressVector av6;
-    alpha.getAddresses(av6, AF_INET6);
-    EXPECT_EQ(0, av6.size());
-
-    // Initialize with V4 addresses only.
-    EXPECT_TRUE(rrv4_.getRdataCount() > 0);
-    NameserverEntry beta(&rrv4_, NULL);
-
-    NameserverEntry::AddressVector bv;
-    beta.getAddresses(bv);
-    EXPECT_EQ(rrv4_.getRdataCount(), bv.size());
-
-    NameserverEntry::AddressVector bv4;
-    beta.getAddresses(bv4, AF_INET);
-    EXPECT_EQ(rrv4_.getRdataCount(), bv4.size());
-
-    NameserverEntry::AddressVector bv6;
-    beta.getAddresses(bv6, AF_INET6);
-    EXPECT_EQ(0, bv6.size());
-
-    // Check that the addresses received are unique.
-    SCOPED_TRACE("Checking V4 addresses");
-    CompareAddresses(bv4, rrv4_);
-
-    // Initialize with V6 addresses only
-    EXPECT_TRUE(rrv6_.getRdataCount() > 0);
-    NameserverEntry gamma(NULL, &rrv6_);
-
-    NameserverEntry::AddressVector cv;
-    gamma.getAddresses(cv);
-    EXPECT_EQ(rrv6_.getRdataCount(), cv.size());
-
-    NameserverEntry::AddressVector cv4;
-    gamma.getAddresses(cv4, AF_INET);
-    EXPECT_EQ(0, cv4.size());
-
-    NameserverEntry::AddressVector cv6;
-    gamma.getAddresses(cv6, AF_INET6);
-    EXPECT_EQ(rrv6_.getRdataCount(), cv6.size());
-
-    SCOPED_TRACE("Checking V6 addresses");
-    CompareAddresses(cv6, rrv6_);
-
-    // Initialize with both sets of addresses
-    NameserverEntry delta(&rrv4_, &rrv6_);
-
-    NameserverEntry::AddressVector dv;
-    delta.getAddresses(dv);
-    EXPECT_EQ((rrv4_.getRdataCount() + rrv6_.getRdataCount()), dv.size());
-
-    NameserverEntry::AddressVector dv4;
-    delta.getAddresses(dv4, AF_INET);
-    EXPECT_EQ(rrv4_.getRdataCount(), dv4.size());
-    SCOPED_TRACE("Checking V4 addresses after dual-address family constructor");
-    CompareAddresses(dv4, rrv4_);
-
-    NameserverEntry::AddressVector dv6;
-    delta.getAddresses(dv6, AF_INET6);
-    EXPECT_EQ(rrv6_.getRdataCount(), dv6.size());
-    SCOPED_TRACE("Checking V6 addresses after dual-address family constructor");
-    CompareAddresses(dv6, rrv6_);
-
-    // ... and check that the composite of the v4 and v6 addresses is the same
-    // as that returned by the get without a filter.
-    NameserverEntry::AddressVector dvcomponent;
-    delta.getAddresses(dvcomponent, AF_INET);
-    delta.getAddresses(dvcomponent, AF_INET6);
-    SCOPED_TRACE("Checking V4+V6 addresses same as composite return");
-    CompareAddressVectors(dv, dvcomponent);
+    // Get the RTT for the different addresses
+    shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(alpha, rrv4_, rrv6_);
+    NameserverEntry::AddressVector vec;
+    alpha->getAddresses(vec);
+
+    // Check they are not 0 and they are all small, they should be some kind
+    // of randomish numbers, so we can't expect much more here
+    BOOST_FOREACH(NameserverAddress& entry, vec) {
+        EXPECT_GT(entry.getAddressEntry().getRTT(), 0);
+        // 20 is some arbitrary small value
+        EXPECT_LT(entry.getAddressEntry().getRTT(), 20);
+    }
 }
 
 // Set an address RTT to a given value
 TEST_F(NameserverEntryTest, SetRTT) {
 
     // Get the RTT for the different addresses
-    NameserverEntry alpha(&rrv4_, &rrv6_);
+    shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(alpha, rrv4_, rrv6_);
     NameserverEntry::AddressVector vec;
-    alpha.getAddresses(vec);
+    alpha->getAddresses(vec);
 
-    EXPECT_TRUE(vec.size() > 0);
+    ASSERT_TRUE(vec.size() > 0);
 
     // Take the first address and change the RTT.
     IOAddress first_address = vec[0].getAddress();
-    uint32_t first_rtt = vec[0].getRTT();
+    uint32_t first_rtt = vec[0].getAddressEntry().getRTT();
     uint32_t new_rtt = first_rtt + 42;
-    alpha.setAddressRTT(first_address, new_rtt);
+    alpha->setAddressRTT(first_address, new_rtt);
 
     // Now see if it has changed
     NameserverEntry::AddressVector newvec;
-    alpha.getAddresses(newvec);
+    alpha->getAddresses(newvec);
 
     int matchcount = 0;
     for (NameserverEntry::AddressVectorIterator i = newvec.begin();
         i != newvec.end(); ++i) {
         if (i->getAddress().equal(first_address)) {
             ++matchcount;
-            EXPECT_EQ(i->getRTT(), new_rtt);
+            EXPECT_EQ(i->getAddressEntry().getRTT(), new_rtt);
         }
     }
 
@@ -318,28 +168,30 @@ TEST_F(NameserverEntryTest, SetRTT) {
 TEST_F(NameserverEntryTest, Unreachable) {
 
     // Get the RTT for the different addresses
-    NameserverEntry alpha(&rrv4_, &rrv6_);
+    shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(alpha, rrv4_, rrv6_);
     NameserverEntry::AddressVector vec;
-    alpha.getAddresses(vec);
+    alpha->getAddresses(vec);
 
-    EXPECT_TRUE(vec.size() > 0);
+    ASSERT_TRUE(vec.size() > 0);
 
     // Take the first address and mark as unreachable.
     IOAddress first_address = vec[0].getAddress();
-    EXPECT_FALSE(vec[0].isUnreachable());
+    EXPECT_FALSE(vec[0].getAddressEntry().isUnreachable());
 
-    alpha.setAddressUnreachable(first_address);
+    alpha->setAddressUnreachable(first_address);
 
     // Now see if it has changed
     NameserverEntry::AddressVector newvec;
-    alpha.getAddresses(newvec);
+    alpha->getAddresses(newvec);
 
     int matchcount = 0;
     for (NameserverEntry::AddressVectorIterator i = newvec.begin();
         i != newvec.end(); ++i) {
         if (i->getAddress().equal(first_address)) {
             ++matchcount;
-            EXPECT_TRUE(i->isUnreachable());
+            EXPECT_TRUE(i->getAddressEntry().isUnreachable());
         }
     }
 
@@ -352,32 +204,42 @@ TEST_F(NameserverEntryTest, Unreachable) {
 // Note that for testing purposes we use the three-argument NameserverEntry
 // constructor (where we supply the time).  It eliminates intermittent errors
 // cause when this test is run just as the clock "ticks over" to another second.
+// TODO Return the way where we can pass time inside somehow
 TEST_F(NameserverEntryTest, ExpirationTime) {
 
     time_t curtime = time(NULL);
     time_t expiration = 0;
 
     // Test where there is a single TTL
-    NameserverEntry alpha(&rrv4_, NULL, curtime);
-    expiration = alpha.getExpiration();
-    EXPECT_EQ(expiration, curtime + rrv4_.getTTL().getValue());
-
-    NameserverEntry beta(NULL, &rrv6_, curtime);
-    expiration = beta.getExpiration();
-    EXPECT_EQ(expiration, curtime + rrv6_.getTTL().getValue());
+    shared_ptr<NameserverEntry> alpha(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(alpha, rrv4_, shared_ptr<BasicRRset>());
+    expiration = alpha->getExpiration();
+    EXPECT_EQ(expiration, curtime + rrv4_->getTTL().getValue());
+
+    shared_ptr<NameserverEntry> beta(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(beta, shared_ptr<BasicRRset>(), rrv6_);
+    expiration = beta->getExpiration();
+    EXPECT_EQ(expiration, curtime + rrv6_->getTTL().getValue());
 
     // Test where there are two different TTLs
-    EXPECT_NE(rrv4_.getTTL().getValue(), rrv6_.getTTL().getValue());
-    NameserverEntry gamma(&rrv4_, &rrv6_, curtime);
-    uint32_t minttl = min(rrv4_.getTTL().getValue(), rrv6_.getTTL().getValue());
+    EXPECT_NE(rrv4_->getTTL().getValue(), rrv6_->getTTL().getValue());
+    shared_ptr<NameserverEntry> gamma(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(gamma, rrv4_, rrv6_);
+    uint32_t minttl = min(rrv4_->getTTL().getValue(), rrv6_->getTTL().getValue());
+    expiration = gamma->getExpiration();
     EXPECT_EQ(expiration, curtime + minttl);
 
     // Finally check where we don't specify a current time.  All we know is
     // that the expiration time should be greater than the TTL (as the current
     // time is greater than zero).
 
-    NameserverEntry delta(&rrv4_, NULL);
-    EXPECT_GT(delta.getExpiration(), rrv4_.getTTL().getValue());
+    shared_ptr<NameserverEntry> delta(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(delta, rrv4_, shared_ptr<BasicRRset>());
+    EXPECT_GT(delta->getExpiration(), rrv4_->getTTL().getValue());
 }
 
 
@@ -385,182 +247,273 @@ TEST_F(NameserverEntryTest, ExpirationTime) {
 TEST_F(NameserverEntryTest, CheckName) {
 
     // Default constructor
-    NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN().getCode());
+    NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::IN());
     EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
-
-    // Address constructor
-    NameserverEntry beta(&rrv4_, NULL);
-    EXPECT_EQ(EXAMPLE_CO_UK, beta.getName());
-
-    NameserverEntry gamma(NULL, &rrv6_);
-    EXPECT_EQ(EXAMPLE_CO_UK, gamma.getName());
-
-    NameserverEntry delta(&rrv4_, &rrv6_);
-    EXPECT_EQ(EXAMPLE_CO_UK, delta.getName());
-
-    // Check that the name is not canonicalised
-    NameserverEntry epsilon(&rrcase_, NULL);
-    EXPECT_EQ(MIXED_EXAMPLE_CO_UK, epsilon.getName());
-
-    // Check that inconsistent names mean that an exception is generated.
-    EXPECT_THROW(NameserverEntry zeta(&rrnet_, &rrv6_),
-        isc::nsas::InconsistentOwnerNames);
 }
 
 // Check that it can cope with non-IN classes.
 TEST_F(NameserverEntryTest, CheckClass) {
 
     // Default constructor
-    NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::CH().getCode());
-    EXPECT_EQ(RRClass::CH().getCode(), alpha.getClass());
+    NameserverEntry alpha(EXAMPLE_CO_UK, RRClass::CH());
+    EXPECT_EQ(RRClass::CH(), alpha.getClass());
+}
 
-    // Address constructor
-    NameserverEntry beta(&rrch_, NULL);
-    EXPECT_EQ(RRClass::CH().getCode(), beta.getClass());
+// Tests if it asks the IP addresses and calls callbacks when it comes
+// including the right addresses are returned and that they expire
+TEST_F(NameserverEntryTest, IPCallbacks) {
+    shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    shared_ptr<Callback> callback(new Callback);
+    shared_ptr<TestResolver> resolver(new TestResolver);
+
+    entry->askIP(resolver, callback, ANY_OK);
+    // Ensure it becomes IN_PROGRESS
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+    // Now, there should be two queries in the resolver
+    EXPECT_EQ(2, resolver->requests.size());
+    ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
+
+    // Another one might ask
+    entry->askIP(resolver, callback, V4_ONLY);
+    // There should still be only two queries in the resolver
+    ASSERT_EQ(2, resolver->requests.size());
+
+    // Another one, with need of IPv6 address
+    entry->askIP(resolver, callback, V6_ONLY);
+
+    // Answer one and see that the callbacks are called
+    resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+        rdata::in::A("192.0.2.1"));
+
+    // Both callbacks that want IPv4 should be called by now
+    EXPECT_EQ(2, callback->count);
+    // It should contain one IP address
+    NameserverEntry::AddressVector addresses;
+    // Still in progress, waiting for the other address
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getAddresses(addresses));
+    EXPECT_EQ(1, addresses.size());
+    // Answer IPv6 address
+    // It is with zero TTL, so it should expire right away
+    resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1"), 0);
+    // The other callback should appear
+    EXPECT_EQ(3, callback->count);
+    // It should return the one address. It should be expired, but
+    // we ignore it for now
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true));
+    // Another address should appear
+    EXPECT_EQ(2, addresses.size());
+    // But when we do not ignore it, it should not appear
+    EXPECT_EQ(Fetchable::EXPIRED, entry->getAddresses(addresses, V6_ONLY));
+    EXPECT_EQ(2, addresses.size());
+}
 
-    // Ensure that inconsistent class throws an exception
-    EXPECT_THROW(NameserverEntry gamma(&rrch_, &rrv6_),
-        isc::nsas::InconsistentClass);
+// Test the callback is called even when the address is unreachable
+TEST_F(NameserverEntryTest, IPCallbacksUnreachable) {
+    shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    shared_ptr<Callback> callback(new Callback);
+    shared_ptr<TestResolver> resolver(new TestResolver);
+
+    // Ask for its IP
+    entry->askIP(resolver, callback, ANY_OK);
+    // Check it asks the resolver
+    EXPECT_EQ(2, resolver->requests.size());
+    ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
+    resolver->requests[0].second->failure();
+    // It should still wait for the second one
+    EXPECT_EQ(0, callback->count);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+    // It should call the callback now and be unrechable
+    resolver->requests[1].second->failure();
+    EXPECT_EQ(1, callback->count);
+    EXPECT_EQ(Fetchable::UNREACHABLE, entry->getState());
+    NameserverEntry::AddressVector addresses;
+    EXPECT_EQ(Fetchable::UNREACHABLE, entry->getAddresses(addresses));
+    EXPECT_EQ(0, addresses.size());
+}
 
+/*
+ * Tests that it works even when we provide the answer right away, directly
+ * from resolve.
+ */
+TEST_F(NameserverEntryTest, DirectAnswer) {
+    shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    shared_ptr<Callback> callback(new Callback);
+    shared_ptr<TestResolver> resolver(new TestResolver);
+    resolver->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+        RRType::A()), rrv4_);
+    resolver->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+        RRType::AAAA()), rrv6_);
+    resolver->addPresetAnswer(Question(Name(EXAMPLE_NET), RRClass::IN(),
+        RRType::A()), shared_ptr<AbstractRRset>());
+    resolver->addPresetAnswer(Question(Name(EXAMPLE_NET), RRClass::IN(),
+        RRType::AAAA()), shared_ptr<AbstractRRset>());
+
+    // A successfull test first
+    entry->askIP(resolver, callback, ANY_OK);
+    EXPECT_EQ(0, resolver->requests.size());
+    EXPECT_EQ(1, callback->count);
+    NameserverEntry::AddressVector addresses;
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses));
+    EXPECT_EQ(5, addresses.size());
+
+    // An unsuccessfull test
+    callback->count = 0;
+    entry.reset(new NameserverEntry(EXAMPLE_NET, RRClass::IN()));
+    entry->askIP(resolver, callback, ANY_OK);
+    EXPECT_EQ(0, resolver->requests.size());
+    EXPECT_EQ(1, callback->count);
+    addresses.clear();
+    EXPECT_EQ(Fetchable::UNREACHABLE, entry->getAddresses(addresses));
+    EXPECT_EQ(0, addresses.size());
 }
 
-// Select one address from the address list
-TEST_F(NameserverEntryTest, AddressSelection) {
-    const int repeats = 100000;
-    boost::shared_ptr<NameserverEntry> ns(new NameserverEntry(&rrv4_, &rrv6_));
-
-    NameserverEntry::AddressVector v4Addresses;
-    NameserverEntry::AddressVector v6Addresses;
-    ns->getAddresses(v4Addresses, AF_INET);
-    ns->getAddresses(v6Addresses, AF_INET6);
-
-    int c1 = 0;
-    int c2 = 0;
-    int c3 = 0;
-    NameserverAddress ns_address;
-    for(int i = 0; i < repeats; ++i){
-        ns.get()->getAddress(ns_address, AF_INET);
-        asiolink::IOAddress io_address = ns_address.getAddress();
-        if(io_address.toText() == v4Addresses[0].getAddress().toText()) ++c1;
-        else if(io_address.toText() == v4Addresses[1].getAddress().toText()) ++c2;
-        else if(io_address.toText() == v4Addresses[2].getAddress().toText()) ++c3;
-    }
-    // We repeat the simulation for N=repeats times
-    // for each address, the probability is p = 1/3, the average mu = N*p
-    // variance sigma^2 = N * p * (1-p) = N * 1/3 * 2/3 = N*2/9
-    // sigma = sqrt(N*2/9)
-    // we should make sure that mu - 4sigma < c < mu + 4sigma
-    // which means for 99.99366% of the time this should be true
-    double p = 1.0 / 3.0;
-    double mu = repeats * p;
-    double sigma = sqrt(repeats * p * (1 - p));
-    ASSERT_TRUE(fabs(c1 - mu) < 4*sigma);
-    ASSERT_TRUE(fabs(c2 - mu) < 4*sigma);
-    ASSERT_TRUE(fabs(c3 - mu) < 4*sigma);
-
-    // update the rtt to 1, 2, 3
-    ns->setAddressRTT(v4Addresses[0].getAddress(), 1);
-    ns->setAddressRTT(v4Addresses[1].getAddress(), 2);
-    ns->setAddressRTT(v4Addresses[2].getAddress(), 3);
-    c1 = c2 = c3 = 0; 
-    for(int i = 0; i < repeats; ++i){
-        ns.get()->getAddress(ns_address, AF_INET);
-        asiolink::IOAddress io_address = ns_address.getAddress();
-        if(io_address.toText() == v4Addresses[0].getAddress().toText()) ++c1;
-        else if(io_address.toText() == v4Addresses[1].getAddress().toText()) ++c2;
-        else if(io_address.toText() == v4Addresses[2].getAddress().toText()) ++c3;
-    }
+/*
+ * This one tests if it works when the data time out and a different
+ * data is received the next time.
+ */
+TEST_F(NameserverEntryTest, ChangedExpired) {
+    shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    shared_ptr<Callback> callback(new Callback);
+    shared_ptr<TestResolver> resolver(new TestResolver);
+
+    // Ask the first time
+    entry->askIP(resolver, callback, V4_ONLY);
+    entry->askIP(resolver, callback, V6_ONLY);
+    EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+    resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+        rdata::in::A("192.0.2.1"), 0);
+    resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1"), 0);
+    EXPECT_EQ(2, callback->count);
+    NameserverEntry::AddressVector addresses;
+    // We must accept expired as well, as the TTL is 0 (and it is OK,
+    // because we just got the callback)
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY, true));
+    ASSERT_EQ(1, addresses.size());
+    EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText());
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true));
+    ASSERT_EQ(2, addresses.size());
+    EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText());
+
+    // Ask the second time. The callbacks should not fire right away and it
+    // should request the addresses again
+    entry->askIP(resolver, callback, V4_ONLY);
+    entry->askIP(resolver, callback, V6_ONLY);
+    EXPECT_EQ(2, callback->count);
+    EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 2, 3));
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+    resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+        rdata::in::A("192.0.2.2"));
+    resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::2"));
+    // We should get the new addresses and they should not expire,
+    // so we should get them without accepting expired
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY));
+    ASSERT_EQ(3, addresses.size());
+    EXPECT_EQ("192.0.2.2", addresses[2].getAddress().toText());
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY));
+    ASSERT_EQ(4, addresses.size());
+    EXPECT_EQ("2001:db8::2", addresses[3].getAddress().toText());
+}
 
-    // We expect that the selection probability for each address that
-    // it will be in the range of [mu-4Sigma, mu+4Sigma]
-    double p1 = 1.0/(1.0 + 1.0/4.0 + 1.0/9.0);
-    double p2 = (1.0/4.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
-    double p3 = (1.0/9.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
-    double mu1 = repeats * 0.7347;
-    double mu2 = repeats * 0.18367;
-    double mu3 = repeats * 0.08163;
-    double sigma1 = sqrt(repeats * p1 * (1 - p1));
-    double sigma2 = sqrt(repeats * p2 * (1 - p2));
-    double sigma3 = sqrt(repeats * p3 * (1 - p3));
-    ASSERT_TRUE(fabs(c1 - mu1) < 4*sigma1);
-    ASSERT_TRUE(fabs(c2 - mu2) < 4*sigma2);
-    ASSERT_TRUE(fabs(c3 - mu3) < 4*sigma3);
-
-    // Test unreachable address
-    ns->setAddressRTT(v4Addresses[0].getAddress(), 1);
-    ns->setAddressRTT(v4Addresses[1].getAddress(), 100);
-    ns->setAddressUnreachable(v4Addresses[2].getAddress());
-    c1 = c2 = c3 = 0;
-    for(int i = 0; i < repeats; ++i){
-        ns.get()->getAddress(ns_address, AF_INET);
-        asiolink::IOAddress io_address = ns_address.getAddress();
-        if(io_address.toText() == v4Addresses[0].getAddress().toText()) ++c1;
-        else if(io_address.toText() == v4Addresses[1].getAddress().toText()) ++c2;
-        else if(io_address.toText() == v4Addresses[2].getAddress().toText()) ++c3;
+/*
+ * When the data expire and is asked again, the original RTT is kept.
+ */
+TEST_F(NameserverEntryTest, KeepRTT) {
+    shared_ptr<NameserverEntry> entry(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    shared_ptr<Callback> callback(new Callback);
+    shared_ptr<TestResolver> resolver(new TestResolver);
+
+    // Ask the first time
+    entry->askIP(resolver, callback, V4_ONLY);
+    entry->askIP(resolver, callback, V6_ONLY);
+    EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+    resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+        rdata::in::A("192.0.2.1"), 0);
+    resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1"), 0);
+    EXPECT_EQ(2, callback->count);
+    NameserverEntry::AddressVector addresses;
+    // We must accept expired as well, as the TTL is 0 (and it is OK,
+    // because we just got the callback)
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY, true));
+    ASSERT_EQ(1, addresses.size());
+    EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText());
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY, true));
+    ASSERT_EQ(2, addresses.size());
+    EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText());
+    BOOST_FOREACH(const NameserverAddress& address, addresses) {
+        entry->setAddressRTT(address.getAddress(), 123);
     }
 
-    // The 3rd address should not be selected again
-    ASSERT_EQ(0, c3);
-
-    // Test if all the servers are unrachable
-    ns->setAddressUnreachable(v4Addresses[0].getAddress());
-    ns->setAddressUnreachable(v4Addresses[1].getAddress());
-    ns->setAddressUnreachable(v4Addresses[2].getAddress());
-    c1 = c2 = c3 = 0;
-    for(int i = 0; i < repeats; ++i){
-        ns.get()->getAddress(ns_address, AF_INET);
-        asiolink::IOAddress io_address = ns_address.getAddress();
-        if(io_address.toText() == v4Addresses[0].getAddress().toText()) ++c1;
-        else if(io_address.toText() == v4Addresses[1].getAddress().toText()) ++c2;
-        else if(io_address.toText() == v4Addresses[2].getAddress().toText()) ++c3;
+    // Ask the second time. The callbacks should not fire right away and it
+    // should request the addresses again
+    entry->askIP(resolver, callback, V4_ONLY);
+    entry->askIP(resolver, callback, V6_ONLY);
+    EXPECT_EQ(2, callback->count);
+    EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 2, 3));
+    EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
+    resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
+        rdata::in::A("192.0.2.1"));
+    resolver->answer(1, Name(EXAMPLE_CO_UK), RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1"));
+    // We should get the new addresses and they should not expire,
+    // so we should get them without accepting expired
+    addresses.clear();
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V4_ONLY));
+    ASSERT_EQ(1, addresses.size());
+    EXPECT_EQ("192.0.2.1", addresses[0].getAddress().toText());
+    EXPECT_EQ(Fetchable::READY, entry->getAddresses(addresses, V6_ONLY));
+    ASSERT_EQ(2, addresses.size());
+    EXPECT_EQ("2001:db8::1", addresses[1].getAddress().toText());
+    // They should have the RTT we set to them
+    BOOST_FOREACH(NameserverAddress& address, addresses) {
+        EXPECT_EQ(123, address.getAddressEntry().getRTT());
     }
-
-    // All the unreachable servers should be selected with equal opportunity
-    ASSERT_EQ(1, (int)(c1*1.0/c2 + 0.5));
-    ASSERT_EQ(1, (int)(c1*1.0/c3 + 0.5));
-    p = 1.0 / 3.0;
-    mu = repeats * p;
-    sigma = sqrt(repeats * p * (1 - p));
-    ASSERT_TRUE(fabs(c1 - mu) < 4*sigma);
-    ASSERT_TRUE(fabs(c2 - mu) < 4*sigma);
-    ASSERT_TRUE(fabs(c3 - mu) < 4*sigma);
-
-    // TODO: The unreachable server should be changed to reachable after 5minutes, but how to test?
 }
 
 // Test the RTT is updated smoothly
 TEST_F(NameserverEntryTest, UpdateRTT) {
-    NameserverEntry ns(&rrv4_, &rrv6_);
+    shared_ptr<NameserverEntry> ns(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN()));
+    fillNSEntry(ns, rrv4_, rrv6_);
     NameserverEntry::AddressVector vec;
-    ns.getAddresses(vec);
+    ns->getAddresses(vec);
 
     // Initialize the rtt with a small value
     uint32_t init_rtt = 1;
-    ns.setAddressRTT(vec[0].getAddress(), init_rtt);
+    ns->setAddressRTT(vec[0].getAddress(), init_rtt);
     // The rtt will be stablized to a large value
     uint32_t stable_rtt = 100;
 
     // Update the rtt
-    ns.updateAddressRTTAtIndex(stable_rtt, 0, AF_INET);
+    vec[0].updateRTT(stable_rtt);
 
     vec.clear();
-    ns.getAddresses(vec);
-    uint32_t new_rtt = vec[0].getRTT();
+    ns->getAddresses(vec);
+    uint32_t new_rtt = vec[0].getAddressEntry().getRTT();
 
     // The rtt should not close to new rtt immediately
-    ASSERT_TRUE((stable_rtt - new_rtt) > (new_rtt - init_rtt));
+    EXPECT_TRUE((stable_rtt - new_rtt) > (new_rtt - init_rtt));
 
     // Update the rtt for enough times
     for(int i = 0; i < 10000; ++i){
-        ns.updateAddressRTTAtIndex(stable_rtt, 0, AF_INET);
+        vec[0].updateRTT(stable_rtt);
     }
     vec.clear();
-    ns.getAddresses(vec);
-    new_rtt = vec[0].getRTT();
+    ns->getAddresses(vec);
+    new_rtt = vec[0].getAddressEntry().getRTT();
 
     // The rtt should be close to stable rtt value
-    ASSERT_TRUE((stable_rtt - new_rtt) < (new_rtt - init_rtt));
+    EXPECT_TRUE((stable_rtt - new_rtt) < (new_rtt - init_rtt));
 }
 
-}   // namespace nsas
-}   // namespace isc
+}   // namespace

+ 5 - 5
src/lib/nsas/tests/nsas_entry_compare_unittest.cc

@@ -21,8 +21,8 @@
 #include <gtest/gtest.h>
 #include <boost/lexical_cast.hpp>
 
-#include "hash_key.h"
-#include "nsas_entry_compare.h"
+#include "../hash_key.h"
+#include "../nsas_entry_compare.h"
 #include "nsas_test.h"
 
 using namespace std;
@@ -39,9 +39,9 @@ class NsasEntryCompareTest : public ::testing::Test {
 TEST_F(NsasEntryCompareTest, Compare) {
 
     // Construct a couple of different objects
-    TestEntry entry1("test1", 42);
-    TestEntry entry2("test1", 24);
-    TestEntry entry3("test2", 42);
+    TestEntry entry1("test1", RRClass(42));
+    TestEntry entry2("test1", RRClass(24));
+    TestEntry entry3("test2", RRClass(42));
 
     // Create corresponding hash key objects
     HashKey key1(entry1.getName(), entry1.getClass());

+ 212 - 17
src/lib/nsas/tests/nsas_test.h

@@ -17,20 +17,24 @@
 #ifndef __NSAS_TEST_H
 #define __NSAS_TEST_H
 
-/// \file test_utilities.h
+/// \file nsas_test.h
 ///
 /// Contains miscellaneous classes and other stuff to help with the nameserver
 /// address store tests.
 
 #include <string>
+#include <vector>
 
-#include "config.h"
+#include <config.h>
 
-#include "buffer.h"
-#include "rdata.h"
-#include "rrtype.h"
-#include "messagerenderer.h"
-#include "nsas_entry.h"
+#include <dns/buffer.h>
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdataclass.h>
+#include "../nsas_entry.h"
+#include "../resolver_interface.h"
 
 using namespace isc::dns::rdata;
 using namespace isc::dns;
@@ -55,12 +59,6 @@ public:
     {return RRType::AAAA().getCode();}
 };
 
-class NS {
-public:
-    uint16_t getType() const
-    {return RRType::NS().getCode();}
-};
-
 class MX {
 public:
     uint16_t getType() const
@@ -155,7 +153,7 @@ public:
     /// \param name Name that will be used for the object.  This will form
     /// part of the key.
     /// \param class_code Class associated with the object.
-    TestEntry(std::string name, uint16_t class_code) :
+    TestEntry(std::string name, const isc::dns::RRClass& class_code) :
         name_(name), class_code_(class_code)
     {}
 
@@ -188,20 +186,20 @@ public:
     /// \brief Get the Class
     ///
     /// \return Class code assigned to this object
-    virtual uint16_t getClass() const {
+    virtual const isc::dns::RRClass& getClass() const {
         return class_code_;
     }
 
     /// \brief Set the Class
     ///
     /// \param class_code New class code of the object
-    virtual void setClass(uint16_t class_code) {
+    virtual void setClass(const isc::dns::RRClass& class_code) {
         class_code_ = class_code;
     }
 
 private:
     std::string name_;          ///< Name of the object
-    uint16_t    class_code_;    ///< Class of the object
+    isc::dns::RRClass    class_code_;    ///< Class of the object
 };
 
 /// \brief isc::nsas Constants
@@ -213,4 +211,201 @@ static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
 } // namespace nsas
 } // namespace isc
 
+namespace {
+
+using namespace std;
+
+/*
+ * This pretends to be a resolver. It stores the queries and
+ * they can be answered.
+ */
+class TestResolver : public isc::nsas::ResolverInterface {
+    private:
+        bool checkIndex(size_t index) {
+            return (requests.size() > index);
+        }
+
+        typedef std::map<isc::dns::Question, boost::shared_ptr<AbstractRRset> >
+            PresetAnswers;
+        PresetAnswers answers_;
+    public:
+        typedef pair<QuestionPtr, CallbackPtr> Request;
+        vector<Request> requests;
+        virtual void resolve(const QuestionPtr& q, const CallbackPtr& c) {
+            PresetAnswers::iterator it(answers_.find(*q));
+            if (it == answers_.end()) {
+                requests.push_back(Request(q, c));
+            } else {
+                if (it->second) {
+                    c->success(it->second);
+                } else {
+                    c->failure();
+                }
+            }
+        }
+
+        /*
+         * Add a preset answer. If shared_ptr() is passed (eg. NULL),
+         * it will generate failure. If the question is not preset,
+         * it goes to requests and you can answer later.
+         */
+        void addPresetAnswer(const isc::dns::Question& question,
+            boost::shared_ptr<AbstractRRset> answer)
+        {
+            answers_[question] = answer;
+        }
+
+        // Thrown if the query at the given index does not exist.
+        class NoSuchRequest : public std::exception { };
+
+        // Thrown if the answer does not match the query
+        class DifferentRequest : public std::exception { };
+
+        QuestionPtr operator[](size_t index) {
+            if (index >= requests.size()) {
+                throw NoSuchRequest();
+            }
+            return (requests[index].first);
+        }
+        /*
+         * Looks if the two provided requests in resolver are A and AAAA.
+         * Sorts them so index1 is A.
+         *
+         * Returns false if there aren't enough elements
+         */
+        bool asksIPs(const Name& name, size_t index1, size_t index2) {
+            size_t max = (index1 < index2) ? index2 : index1;
+            if (!checkIndex(max)) {
+                return false;
+            }
+            EXPECT_EQ(name, (*this)[index1]->getName());
+            EXPECT_EQ(name, (*this)[index2]->getName());
+            EXPECT_EQ(RRClass::IN(), (*this)[index1]->getClass());
+            EXPECT_EQ(RRClass::IN(), (*this)[index2]->getClass());
+            // If they are the other way around, swap
+            if ((*this)[index1]->getType() == RRType::AAAA() &&
+                (*this)[index2]->getType() == RRType::A())
+            {
+                TestResolver::Request tmp((*this).requests[index1]);
+                (*this).requests[index1] =
+                    (*this).requests[index2];
+                (*this).requests[index2] = tmp;
+            }
+            // Check the correct addresses
+            EXPECT_EQ(RRType::A(), (*this)[index1]->getType());
+            EXPECT_EQ(RRType::AAAA(), (*this)[index2]->getType());
+            return (true);
+        }
+
+        /*
+         * Sends a simple answer to a query.
+         * Provide index of a query and the address to pass.
+         */
+        void answer(size_t index, const Name& name, const RRType& type,
+            const rdata::Rdata& rdata, size_t TTL = 100)
+        {
+            if (index >= requests.size()) {
+                throw NoSuchRequest();
+            }
+            RRsetPtr set(new RRset(name, RRClass::IN(),
+                type, RRTTL(TTL)));
+            set->addRdata(rdata);
+            requests[index].second->success(set);
+        }
+
+        void provideNS(size_t index,
+            boost::shared_ptr<AbstractRRset> nameservers)
+        {
+            if (index >= requests.size()) {
+                throw NoSuchRequest();
+            }
+            if (requests[index].first->getName() != nameservers->getName() ||
+                requests[index].first->getType() != RRType::NS())
+            {
+                throw DifferentRequest();
+            }
+            requests[index].second->success(nameservers);
+        }
+};
+
+// String constants.  These should end in a dot.
+static const std::string EXAMPLE_CO_UK("example.co.uk.");
+static const std::string EXAMPLE_NET("example.net.");
+static const std::string MIXED_EXAMPLE_CO_UK("EXAmple.co.uk.");
+
+class TestWithRdata : public ::testing::Test {
+protected:
+    typedef boost::shared_ptr<RRset> RRsetPtr;
+    /// \brief Constructor
+    ///
+    /// Initializes the RRsets used in the tests.  The RRsets themselves have to
+    /// be initialized with the basic data on their construction. The Rdata for
+    /// them is added in SetUp().
+    TestWithRdata() :
+        rrv4_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::A(),
+            RRTTL(1200))),
+        rrcase_(new RRset(Name(MIXED_EXAMPLE_CO_UK), RRClass::IN(),
+            RRType::A(), RRTTL(1200))),
+        rrch_(new RRset(Name(EXAMPLE_CO_UK), RRClass::CH(), RRType::A(),
+            RRTTL(1200))),
+        rrns_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(), RRType::NS(),
+            RRTTL(1200))),
+        rr_single_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+            RRType::NS(), RRTTL(600))),
+        rr_empty_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+            RRType::NS(), RRTTL(600))),
+        rrv6_(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+            RRType::AAAA(), RRTTL(900))),
+        rrnet_(new RRset(Name(EXAMPLE_NET), RRClass::IN(), RRType::A(),
+            RRTTL(600))),
+        ns_name_("ns.example.net.")
+    {}
+
+    /// \brief Add Rdata to RRsets
+    ///
+    /// The data are added as const pointers to avoid the stricter type checking
+    /// applied by the Rdata code.  There is no need for it in these tests.
+    virtual void SetUp() {
+
+        // A records
+        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
+        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("5.6.7.8")));
+        rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
+
+        // A records
+        rrcase_->addRdata(ConstRdataPtr(new RdataTest<A>("13.14.15.16")));
+
+        // No idea what Chaosnet address look like other than they are 16 bits
+        // The fact that they are type A is probably also incorrect.
+        rrch_->addRdata(ConstRdataPtr(new RdataTest<A>("1324")));
+
+        // NS records take a single name
+        rrns_->addRdata(rdata::generic::NS("example.fr"));
+        rrns_->addRdata(rdata::generic::NS("example.de"));
+
+        // Single NS record with 0 TTL
+        rr_single_->addRdata(rdata::generic::NS(ns_name_));
+
+        // AAAA records
+        rrv6_->addRdata(ConstRdataPtr(new RdataTest<AAAA>("2001::1002")));
+        rrv6_->addRdata(ConstRdataPtr(new RdataTest<AAAA>("dead:beef:feed::")));
+
+        // A record for example.net
+        rrnet_->addRdata(ConstRdataPtr(new RdataTest<A>("17.18.18.20")));
+    }
+
+    /// \brief Data for the tests
+    RRsetPtr rrv4_;           ///< Standard RRSet - IN, A, lowercase name
+    RRsetPtr rrcase_;         ///< Mixed-case name
+    RRsetPtr rrch_;           ///< Non-IN RRset (Chaos in this case)
+    RRsetPtr rrns_;           ///< NS RRset
+    RRsetPtr rr_single_;      ///< NS RRset with single NS
+    RRsetPtr rr_empty_;       ///< NS RRset without any nameservers
+    RRsetPtr rrv6_;           ///< Standard RRset, IN, AAAA, lowercase name
+    RRsetPtr rrnet_;          ///< example.net A RRset
+    Name ns_name_;  ///< Nameserver name of ns.example.net
+};
+
+} // Empty namespace
+
 #endif // __NSAS_TEST_H

+ 713 - 12
src/lib/nsas/tests/zone_entry_unittest.cc

@@ -15,38 +15,739 @@
 // $Id$
 
 #include <gtest/gtest.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <cmath>
 
-#include "rrclass.h"
+#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
 
-#include "asiolink.h"
-#include "zone_entry.h"
+#include "../asiolink.h"
+#include "../zone_entry.h"
+#include "../nameserver_entry.h"
+#include "../address_request_callback.h"
+#include "../nsas_entry_compare.h"
+#include "../hash_deleter.h"
 
 #include "nsas_test.h"
 
+using namespace isc::nsas;
 using namespace asiolink;
 using namespace std;
+using namespace boost;
 using namespace isc::dns;
 
-namespace isc {
-namespace nsas {
+namespace {
 
-// String constants.  These should end in a dot.
-static const std::string EXAMPLE_CO_UK("example.co.uk.");
+/// \brief Inherited version with access into its internals for tests
+class InheritedZoneEntry : public ZoneEntry {
+    public:
+        InheritedZoneEntry(shared_ptr<ResolverInterface> resolver,
+            const std::string& name, const RRClass& class_code,
+            shared_ptr<HashTable<NameserverEntry> > nameserver_table,
+            shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
+            ZoneEntry(resolver, name, class_code, nameserver_table,
+                nameserver_lru)
+        { }
+        NameserverVector& nameservers() { return nameservers_; }
+};
 
 /// \brief Test Fixture Class
-class ZoneEntryTest : public ::testing::Test {
+class ZoneEntryTest : public TestWithRdata {
 protected:
+    /// \brief Constructor
+    ZoneEntryTest() :
+        nameserver_table_(new HashTable<NameserverEntry>(
+            new NsasEntryCompare<NameserverEntry>)),
+        nameserver_lru_(new LruList<NameserverEntry>(
+            (3 * nameserver_table_->tableSize()),
+            new HashDeleter<NameserverEntry>(*nameserver_table_))),
+        resolver_(new TestResolver),
+        callback_(new Callback)
+    { }
+    /// \brief Tables of nameservers to pass into zone entry constructor
+    shared_ptr<HashTable<NameserverEntry> > nameserver_table_;
+    shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
+    /// \brief The resolver
+    shared_ptr<TestResolver> resolver_;
+
+    /**
+     * \short A callback we use into the zone.
+     *
+     * It counts failures and stores successufll results.
+     */
+    struct Callback : public AddressRequestCallback {
+        Callback() : unreachable_count_(0) {}
+        size_t unreachable_count_;
+        vector<NameserverAddress> successes_;
+        virtual void unreachable() { unreachable_count_ ++; }
+        virtual void success(const NameserverAddress& address) {
+            successes_.push_back(address);
+        }
+    };
+    shared_ptr<Callback> callback_;
+
+    // Empty callback to pass to nameserver entry, to do injection of them
+    struct NseCallback : public NameserverEntry::Callback {
+        virtual void operator()(shared_ptr<NameserverEntry>) { }
+    };
+
+    shared_ptr<NseCallback> nseCallback() {
+        return (shared_ptr<NseCallback>(new NseCallback));
+    }
 
+    /**
+     * \short Function returning a new zone.
+     *
+     * Convenience funcion, just creating a new zone, to shorten the code.
+     */
+    shared_ptr<InheritedZoneEntry> getZone() {
+        return (shared_ptr<InheritedZoneEntry>(new InheritedZoneEntry(
+            resolver_, EXAMPLE_CO_UK, RRClass::IN(), nameserver_table_,
+            nameserver_lru_)));
+    }
+
+    /**
+     * \short Creates and injects a NameserverEntry
+     *
+     * This is used by few tests checking it works when the nameserver
+     * hash table already contains the NameserverEntry. This function
+     * creates one and puts it into the hash table.
+     */
+    shared_ptr<NameserverEntry> injectEntry() {
+        shared_ptr<NameserverEntry> nse(new NameserverEntry(ns_name_.toText(),
+            RRClass::IN()));
+        nameserver_table_->add(nse, HashKey(ns_name_.toText(), RRClass::IN()));
+        EXPECT_EQ(Fetchable::NOT_ASKED, nse->getState());
+        return (nse);
+    }
+
+    /**
+     * \short Common part of few tests.
+     *
+     * All the tests NameserverEntryReady, NameserverEntryNotAsked,
+     * NameserverEntryInProgress, NameserverEntryExpired,
+     * NameserverEntryUnreachable check that it does not break
+     * when the nameserver hash table already contains the nameserver
+     * in one of the Fetchable::State.
+     *
+     * All the tests prepare the NameserverEntry and then call this
+     * to see if the zone really works. This asks and checks if it
+     * asks for the IP addresses or not and if it succeeds or fails.
+     *
+     * \param answer Should it ask for IP addresses of the nameserver?
+     *     If not, it expects it already asked during the preparation
+     *     (therefore the request count in resolver is 2).
+     * \param success_count How many callbacks to the zone should
+     *     succeed.
+     * \param failure_count How many callbacks to the zone should
+     *     fail.
+     */
+    void checkInjected(bool answer, size_t success_count = 1,
+        size_t failure_count = 0)
+    {
+        // Create the zone and check it acts correctly
+        shared_ptr<InheritedZoneEntry> zone(getZone());
+        resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+            RRType::NS()), rr_single_);
+        zone->addCallback(callback_, ANY_OK);
+        EXPECT_EQ(2, resolver_->requests.size());
+        EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
+        if (answer) {
+            EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
+                rdata::in::A("192.0.2.1")));
+            EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
+                rdata::in::AAAA("2001:db8::1")));
+        }
+        // Check the answers
+        EXPECT_EQ(Fetchable::READY, zone->getState());
+        EXPECT_EQ(failure_count, callback_->unreachable_count_);
+        EXPECT_EQ(success_count, callback_->successes_.size());
+        for (size_t i = 0; i < callback_->successes_.size(); ++ i) {
+            EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+                callback_->successes_[i].getAddress()) ||
+                IOAddress("2001:db8::1").equal(
+                callback_->successes_[i].getAddress()));
+        }
+    }
 };
 
 /// Tests of the default constructor
 TEST_F(ZoneEntryTest, DefaultConstructor) {
 
     // Default constructor should not create any RRsets
-    ZoneEntry alpha(EXAMPLE_CO_UK, RRClass::IN().getCode());
+    InheritedZoneEntry alpha(resolver_, EXAMPLE_CO_UK,
+        RRClass::IN(), nameserver_table_, nameserver_lru_);
     EXPECT_EQ(EXAMPLE_CO_UK, alpha.getName());
-    EXPECT_EQ(RRClass::IN().getCode(), alpha.getClass());
+    EXPECT_EQ(RRClass::IN(), alpha.getClass());
+    EXPECT_TRUE(alpha.nameservers().empty());
+}
+
+/**
+ * \short Test with no nameservers.
+ *
+ * When we create a zone that does not have any nameservers,
+ * it should return failures right away (eg. no queries to nameservers
+ * should be generated anywhere and the failure should be provided).
+ */
+TEST_F(ZoneEntryTest, CallbackNoNS) {
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    // feed it with a callback
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    // Give it the (empty) nameserver list
+    EXPECT_NO_THROW(resolver_->provideNS(0, rr_empty_));
+    // It should tell us it is unreachable.
+    EXPECT_TRUE(callback_->successes_.empty());
+    EXPECT_EQ(1, callback_->unreachable_count_);
+}
+
+/**
+ * \short Test how the zone behaves when the list of nameserves change.
+ *
+ * We use TTL of 0, so it asks every time for new list of nameservers.
+ * This allows us to pass a different list each time.
+ *
+ * So, this implicitly tests that it behaves correctly with 0 TTL as well,
+ * it means that it answers even with 0 TTL and that it answers only
+ * one query (or, all queries queued at that time).
+ *
+ * We change the list twice, to see it can ask for another nameserver and
+ * then to see if it can return to the previous (already cached) nameserver.
+ */
+TEST_F(ZoneEntryTest, ChangedNS) {
+    // Make it zero TTL, so it expires right away
+    rr_single_->setTTL(RRTTL(0));
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    // Feed it with callback
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+    // It should not be answered yet, it should ask for the IP addresses
+    // (trough the NameserverEntry there)
+    EXPECT_TRUE(callback_->successes_.empty());
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+    EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
+        rdata::in::A("192.0.2.1")));
+    ASSERT_EQ(1, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[0].getAddress()));
+    EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1")));
+    EXPECT_EQ(1, callback_->successes_.size());
+    // It should request the NSs again, as TTL is 0
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(4, resolver_->requests.size());
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+
+    Name different_name("ns.example.com");
+    // Create a different answer
+    RRsetPtr different_ns(new RRset(Name(EXAMPLE_CO_UK), RRClass::IN(),
+        RRType::NS(), RRTTL(0)));
+    different_ns->addRdata(rdata::generic::NS(different_name));
+    EXPECT_NO_THROW(resolver_->provideNS(3, different_ns));
+    // It should become ready and ask for the new nameserver addresses
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // Answer one of the IP addresses, we should get an address now
+    EXPECT_TRUE(resolver_->asksIPs(different_name, 4, 5));
+    EXPECT_NO_THROW(resolver_->answer(4, different_name, RRType::A(),
+        rdata::in::A("192.0.2.2")));
+    ASSERT_EQ(2, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.2").equal(
+        callback_->successes_[1].getAddress()));
+
+    // And now, switch back, as it timed out again
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(7, resolver_->requests.size());
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    // When we answer with the original, it should still be cached and
+    // we should get the answer
+    EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+    EXPECT_EQ(7, resolver_->requests.size());
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    ASSERT_EQ(3, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[0].getAddress()));
+}
+
+/**
+ * \short Check it works when everything is answered.
+ *
+ * This test emulates a situation when all queries for NS rrsets and
+ * IP addresses (of the NameserverEntry objects inside) are answered
+ * positively. All the callbacks should be called and answer to them
+ * provided.
+ */
+TEST_F(ZoneEntryTest, CallbacksAnswered) {
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    // Feed it with a callback
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+    // It should not be answered yet, its NameserverEntry should ask for the
+    // IP addresses
+    EXPECT_TRUE(callback_->successes_.empty());
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+    // We should be READY, as it marks we have nameservers
+    // (not that they are ready)
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // Give two more callbacks, with different address families
+    zone->addCallback(callback_, V4_ONLY);
+    zone->addCallback(callback_, V6_ONLY);
+    // Nothing more is asked
+    EXPECT_EQ(3, resolver_->requests.size());
+    EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
+         rdata::in::A("192.0.2.1")));
+    // Two are answered (ANY and V4)
+    ASSERT_EQ(2, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[0].getAddress()));
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[1].getAddress()));
+    // None are rejected
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    // Answer the IPv6 one as well
+    EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1")));
+    // This should answer the third callback
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    ASSERT_EQ(3, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+        callback_->successes_[2].getAddress()));
+    // It should think it is ready
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // When we ask something more, it should be answered right away
+    zone->addCallback(callback_, V4_ONLY);
+    EXPECT_EQ(3, resolver_->requests.size());
+    ASSERT_EQ(4, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[3].getAddress()));
+    EXPECT_EQ(0, callback_->unreachable_count_);
+}
+
+/**
+ * \short Test zone reachable only on IPv4.
+ *
+ * This test simulates a zone with its nameservers reachable only
+ * over IPv4. It means we answer the NS query, the A query, but
+ * we generate a failure for AAAA.
+ *
+ * The callbacks asking for any address and IPv4 address should be
+ * called successfully, while the ones asking for IPv6 addresses should
+ * fail.
+ */
+TEST_F(ZoneEntryTest, CallbacksAOnly) {
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+    // It should not be answered yet, it should ask for the IP addresses
+    EXPECT_TRUE(callback_->successes_.empty());
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // Give two more callbacks, with different address families
+    zone->addCallback(callback_, V4_ONLY);
+    zone->addCallback(callback_, V6_ONLY);
+    ASSERT_GE(resolver_->requests.size(), 3);
+    // We tell its NameserverEntry we can't get IPv6 address
+    resolver_->requests[2].second->failure();
+    // One should be rejected (V6_ONLY one), but two still stay
+    EXPECT_EQ(0, callback_->successes_.size());
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    // Answer the A one and see it answers what can be answered
+    EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
+        rdata::in::A("192.0.2.1")));
+    ASSERT_EQ(2, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[0].getAddress()));
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[1].getAddress()));
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    // Everything arriwed, so we are ready
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // Try asking something more and see it asks no more
+    zone->addCallback(callback_, V4_ONLY);
+    EXPECT_EQ(3, resolver_->requests.size());
+    ASSERT_EQ(3, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[2].getAddress()));
+    EXPECT_EQ(1, callback_->unreachable_count_);
+
+    zone->addCallback(callback_, V6_ONLY);
+    EXPECT_EQ(3, resolver_->requests.size());
+    EXPECT_EQ(3, callback_->successes_.size());
+    EXPECT_EQ(2, callback_->unreachable_count_);
+}
+
+/**
+ * \short Test with unreachable and v6-reachable nameserver.
+ *
+ * In this test we have a zone with two nameservers. The first one of them
+ * is unreachable, it will not have any addresses. We check that the ZoneEntry
+ * is patient and asks and waits for the second one and then returns the
+ * (successful) answers to us.
+ */
+TEST_F(ZoneEntryTest, CallbackTwoNS) {
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    zone->addCallback(callback_, V4_ONLY);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    EXPECT_NO_THROW(resolver_->provideNS(0, rrns_));
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // It asks a question (we do not know which nameserver)
+    shared_ptr<Name> name;
+    ASSERT_NO_THROW(name.reset(new Name((*resolver_)[1]->getName())));
+    ASSERT_TRUE(resolver_->asksIPs(*name, 1, 2));
+    resolver_->requests[1].second->failure();
+    // Nothing should be answered or failed yet, there's second one
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_EQ(0, callback_->successes_.size());
+    ASSERT_TRUE(resolver_->asksIPs((*resolver_)[3]->getName(), 3, 4));
+    // Fail the second one
+    resolver_->requests[3].second->failure();
+    // The callback should be failed now, as there is no chance of getting
+    // v4 address
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    EXPECT_EQ(0, callback_->successes_.size());
+    // And question for v6 or any should still wait while v4 should be failed
+    // right away
+    zone->addCallback(callback_, V6_ONLY);
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    EXPECT_EQ(0, callback_->successes_.size());
+
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    EXPECT_EQ(0, callback_->successes_.size());
+
+    zone->addCallback(callback_, V4_ONLY);
+    EXPECT_EQ(2, callback_->unreachable_count_);
+    EXPECT_EQ(0, callback_->successes_.size());
+    // Answer the IPv6 one
+    EXPECT_NO_THROW(resolver_->answer(2, (*resolver_)[2]->getName(),
+        RRType::AAAA(), rdata::in::AAAA("2001:db8::1")));
+
+    // Ready, as we have at last some address
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // The other callbacks should be answered now
+    EXPECT_EQ(2, callback_->unreachable_count_);
+    ASSERT_EQ(2, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+        callback_->successes_[0].getAddress()));
+    EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+        callback_->successes_[1].getAddress()));
+}
+
+/**
+ * \short This test checks it works with answers from cache.
+ *
+ * The resolver might provide the answer by calling the callback both sometime
+ * later or directly from its resolve method, causing recursion back inside
+ * the ZoneEntry. This test checks it works even in the second case (eg. that
+ * the ZoneEntry is able to handle callback called directly from the
+ * resolve). Tries checking both positive and negative answers.
+ */
+TEST_F(ZoneEntryTest, DirectAnswer) {
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+
+    // One unsuccessfull attempt, nameservers fail
+    resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+        RRType::NS()), shared_ptr<AbstractRRset>());
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(0, callback_->successes_.size());
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    EXPECT_EQ(0, resolver_->requests.size());
+    EXPECT_EQ(Fetchable::UNREACHABLE, zone->getState());
+
+    // Successfull attempt now
+    zone = getZone();
+    // First, fill the answers to all the questions it should ask
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    resolver_->addPresetAnswer(Question(Name(EXAMPLE_CO_UK), RRClass::IN(),
+        RRType::NS()), rr_single_);
+    Name ns_name("ns.example.net");
+    rrv4_->setName(ns_name);
+    resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(), RRType::A()),
+        rrv4_);
+    rrv6_->setName(ns_name);
+    resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(),
+        RRType::AAAA()), rrv6_);
+    // Reset the results
+    callback_->unreachable_count_ = 0;
+    // Ask for the IP address
+    zone->addCallback(callback_, ANY_OK);
+    // It should be answered right away, positively
+    EXPECT_EQ(1, callback_->successes_.size());
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_EQ(0, resolver_->requests.size());
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+
+    // Reset the results
+    callback_->successes_.clear();
+    // Now, pretend we do not have IP addresses
+    resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(), RRType::A()),
+        shared_ptr<AbstractRRset>());
+    resolver_->addPresetAnswer(Question(ns_name, RRClass::IN(),
+        RRType::AAAA()), shared_ptr<AbstractRRset>());
+    // Get another zone and ask it again. It should fail.
+    // Clean the table first, though, so it does not find the old nameserver
+    nameserver_table_->remove(HashKey(ns_name.toText(), RRClass::IN()));
+    zone = getZone();
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(0, callback_->successes_.size());
+    EXPECT_EQ(1, callback_->unreachable_count_);
+    EXPECT_EQ(0, resolver_->requests.size());
+    // It should be ready, but have no IP addresses on the nameservers
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+}
+
+/**
+ * \short Test it works with timeouting NameserverEntries.
+ *
+ * In this test we have a zone with nameserver addresses at TTL 0.
+ * So, the NameserverEntry expires each time the ZoneEntry tries to get
+ * its addresses and must ask it again.
+ */
+TEST_F(ZoneEntryTest, AddressTimeout) {
+    shared_ptr<InheritedZoneEntry> zone(getZone());
+    EXPECT_EQ(Fetchable::NOT_ASKED, zone->getState());
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
+    EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
+    // It should not be answered yet, it should ask for the IP addresses
+    EXPECT_TRUE(callback_->successes_.empty());
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
+    // We should be READY, as it marks we have nameservers
+    // (not that they are ready)
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
+         rdata::in::A("192.0.2.1"), 0));
+    // It answers, not rejects
+    ASSERT_EQ(1, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[0].getAddress()));
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    // As well with IPv6
+    EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1"), 0));
+    EXPECT_EQ(1, callback_->successes_.size());
+    EXPECT_EQ(Fetchable::READY, zone->getState());
+    // When we ask for another one, it should ask for the addresses again
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 3, 4));
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    EXPECT_EQ(1, callback_->successes_.size());
+    EXPECT_NO_THROW(resolver_->answer(3, ns_name_, RRType::A(),
+         rdata::in::A("192.0.2.1"), 0));
+    EXPECT_EQ(0, callback_->unreachable_count_);
+    ASSERT_EQ(2, callback_->successes_.size());
+    EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+        callback_->successes_[1].getAddress()));
+}
+
+/**
+ * \short Injection tests.
+ *
+ * These tests check the ZoneEntry does not break when the nameserver hash
+ * table already contains a NameserverEntry in some given state. Each test
+ * for a different state.
+ */
+//@{
+
+/// \short Test how the zone reacts to a nameserver entry in ready state
+TEST_F(ZoneEntryTest, NameserverEntryReady) {
+    // Inject the entry
+    shared_ptr<NameserverEntry> nse(injectEntry());
+    // Fill it with data
+    nse->askIP(resolver_, nseCallback(), ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
+    EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
+        rdata::in::A("192.0.2.1")));
+    EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1")));
+    EXPECT_EQ(Fetchable::READY, nse->getState());
+
+    checkInjected(false);
+}
+
+/// \short Test how the zone reacts to a nameserver in not asked state
+TEST_F(ZoneEntryTest, NameserverEntryNotAsked) {
+    // Inject the entry
+    injectEntry();
+    // We do not need it, nothing to modify on it
+
+    checkInjected(true);
+}
+
+/// \short What if the zone finds a nameserver in progress?
+TEST_F(ZoneEntryTest, NameserverEntryInProgress) {
+    // Prepare the nameserver entry
+    shared_ptr<NameserverEntry> nse(injectEntry());
+    nse->askIP(resolver_, nseCallback(), ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
+
+    checkInjected(true);
+}
+
+/// \short Check Zone's reaction to found expired nameserver
+TEST_F(ZoneEntryTest, NameserverEntryExpired) {
+    shared_ptr<NameserverEntry> nse(injectEntry());
+    nse->askIP(resolver_, nseCallback(), ANY_OK);
+    EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
+    EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
+    EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
+        rdata::in::A("192.0.2.1"), 0));
+    EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::1"), 0));
+    EXPECT_EQ(Fetchable::READY, nse->getState());
+    NameserverEntry::AddressVector addresses;
+    EXPECT_EQ(Fetchable::EXPIRED, nse->getAddresses(addresses));
+    EXPECT_EQ(Fetchable::EXPIRED, nse->getState());
+    resolver_->requests.clear();
+
+    checkInjected(true);
+}
+
+/// \short Check how it reacts to an unreachable zone already in the table
+TEST_F(ZoneEntryTest, NameserverEntryUnreachable) {
+    shared_ptr<NameserverEntry> nse(injectEntry());
+    nse->askIP(resolver_, nseCallback(), ANY_OK);
+    ASSERT_EQ(2, resolver_->requests.size());
+    resolver_->requests[0].second->failure();
+    resolver_->requests[1].second->failure();
+    EXPECT_EQ(Fetchable::UNREACHABLE, nse->getState());
+
+    checkInjected(false, 0, 1);
+}
+
+//@}
+
+// Count hits of each address
+void
+countHits(size_t *counts, const vector<NameserverAddress>& successes) {
+    BOOST_FOREACH(const NameserverAddress& address, successes) {
+        // We use the last digit as an index
+        string address_string(address.getAddress().toText());
+        size_t index(address_string[address_string.size() - 1] - '0' - 1);
+        ASSERT_LT(index, 3);
+        counts[index] ++;
+    }
+}
+
+// Select one address from the address list
+TEST_F(ZoneEntryTest, AddressSelection) {
+    const size_t repeats = 100000;
+    // Create the zone, give it 2 nameservers and total of 3 addresses
+    // (one of them is ipv6)
+    shared_ptr<ZoneEntry> zone(getZone());
+    zone->addCallback(callback_, ANY_OK);
+    EXPECT_NO_THROW(resolver_->provideNS(0, rrns_));
+    ASSERT_GT(resolver_->requests.size(), 1);
+    Name name1(resolver_->requests[1].first->getName());
+    EXPECT_TRUE(resolver_->asksIPs(name1, 1, 2));
+    resolver_->answer(1, name1, RRType::A(), rdata::in::A("192.0.2.1"));
+    resolver_->answer(2, name1, RRType::AAAA(),
+        rdata::in::AAAA("2001:db8::2"));
+    ASSERT_GT(resolver_->requests.size(), 3);
+    Name name2(resolver_->requests[3].first->getName());
+    EXPECT_TRUE(resolver_->asksIPs(name2, 3, 4));
+    resolver_->answer(3, name2, RRType::A(), rdata::in::A("192.0.2.3"));
+    resolver_->requests[4].second->failure();
+
+    shared_ptr<NameserverEntry> ns1(nameserver_table_->get(HashKey(
+        name1.toText(), RRClass::IN()))),
+        ns2(nameserver_table_->get(HashKey(name2.toText(), RRClass::IN())));
+
+    size_t counts[3] = {0, 0, 0};
+    callback_->successes_.clear();
+
+    // Test they have the same probabilities when they have the same RTT
+    for (size_t i(0); i < repeats; ++ i) {
+        zone->addCallback(callback_, ANY_OK);
+    }
+    countHits(counts, callback_->successes_);
+    // We repeat the simulation for N=repeats times
+    // for each address, the probability is p = 1/3, the average mu = N*p
+    // variance sigma^2 = N * p * (1-p) = N * 1/3 * 2/3 = N*2/9
+    // sigma = sqrt(N*2/9)
+    // we should make sure that mu - 4sigma < c < mu + 4sigma
+    // which means for 99.99366% of the time this should be true
+    double p = 1.0 / 3.0;
+    double mu = repeats * p;
+    double sigma = sqrt(repeats * p * (1 - p));
+    for (size_t i(0); i < 3; ++ i) {
+        ASSERT_TRUE(fabs(counts[i] - mu) < 4*sigma);
+    }
+
+    // reset the environment
+    callback_->successes_.clear();
+    counts[0] = counts[1] = counts[2] = 0;
+
+    // Test when the RTT is not the same
+    ns1->setAddressRTT(IOAddress("192.0.2.1"), 1);
+    ns1->setAddressRTT(IOAddress("2001:db8::2"), 2);
+    ns2->setAddressRTT(IOAddress("192.0.2.3"), 3);
+    for (size_t i(0); i < repeats; ++ i) {
+        zone->addCallback(callback_, ANY_OK);
+    }
+    countHits(counts, callback_->successes_);
+    // We expect that the selection probability for each address that
+    // it will be in the range of [mu-4Sigma, mu+4Sigma]
+    double ps[3];
+    ps[0] = 1.0/(1.0 + 1.0/4.0 + 1.0/9.0);
+    ps[1] = (1.0/4.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
+    ps[2] = (1.0/9.0)/(1.0 + 1.0/4.0 + 1.0/9.0);
+    for (size_t i(0); i < 3; ++ i) {
+        double mu = repeats * ps[i];
+        double sigma = sqrt(repeats * ps[i] * (1 - ps[i]));
+        ASSERT_TRUE(fabs(counts[i] - mu < 4 * sigma));
+    }
+
+    // reset the environment
+    callback_->successes_.clear();
+    counts[0] = counts[1] = counts[2] = 0;
+
+    // Test with unreachable address
+    ns1->setAddressRTT(IOAddress("192.0.2.1"), 1);
+    ns1->setAddressRTT(IOAddress("2001:db8::2"), 100);
+    ns2->setAddressUnreachable(IOAddress("192.0.2.3"));
+    for (size_t i(0); i < repeats; ++ i) {
+        zone->addCallback(callback_, ANY_OK);
+    }
+    countHits(counts, callback_->successes_);
+    // The unreachable one shouldn't be called
+    EXPECT_EQ(0, counts[2]);
+
+    // reset the environment
+    callback_->successes_.clear();
+    counts[0] = counts[1] = counts[2] = 0;
+
+    // Test with all unreachable
+    ns1->setAddressUnreachable(IOAddress("192.0.2.1"));
+    ns1->setAddressUnreachable(IOAddress("2001:db8::2"));
+    ns2->setAddressUnreachable(IOAddress("192.0.2.3"));
+    for (size_t i(0); i < repeats; ++ i) {
+        zone->addCallback(callback_, ANY_OK);
+    }
+    countHits(counts, callback_->successes_);
+    // They should have about the same probability
+    for (size_t i(0); i < 3; ++ i) {
+        ASSERT_TRUE(fabs(counts[i] - mu) < 4*sigma);
+    }
+
+    // TODO: The unreachable server should be changed to reachable after 5minutes, but how to test?
 }
 
-}   // namespace nsas
-}   // namespace isc
+}   // namespace

+ 527 - 0
src/lib/nsas/zone_entry.cc

@@ -0,0 +1,527 @@
+// Copyright (C) 2010  CZ NIC
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $id$
+
+#include "zone_entry.h"
+#include "address_request_callback.h"
+#include "nameserver_entry.h"
+
+#include <algorithm>
+#include <boost/foreach.hpp>
+#include <boost/random.hpp>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace boost;
+
+namespace isc {
+
+using namespace dns;
+
+namespace nsas {
+
+ZoneEntry::ZoneEntry(boost::shared_ptr<ResolverInterface> resolver,
+    const std::string& name, const isc::dns::RRClass& class_code,
+    boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
+    boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
+    expiry_(0),
+    name_(name), class_code_(class_code), resolver_(resolver),
+    nameserver_table_(nameserver_table), nameserver_lru_(nameserver_lru)
+{
+    in_process_[ANY_OK] = false;
+    in_process_[V4_ONLY] = false;
+    in_process_[V6_ONLY] = false;
+}
+
+namespace {
+// Shorter aliases for frequently used types
+typedef recursive_mutex::scoped_lock Lock; // Local lock, nameservers not locked
+typedef shared_ptr<AddressRequestCallback> CallbackPtr;
+
+/*
+ * Create a nameserver.
+ * Called inside a mutex so it is filled in atomically.
+ */
+shared_ptr<NameserverEntry>
+newNs(const std::string* name, const RRClass* class_code) {
+    return (shared_ptr<NameserverEntry>(new NameserverEntry(*name,
+        *class_code)));
+}
+
+}
+
+/**
+ * \short Callback class that ZoneEntry passes to a resolver.
+ *
+ * We need to ask for the list of nameservers. So we pass ResolverCallback
+ * object to it, when it knows the answer, method of this thing will be
+ * called.
+ *
+ * It is a nested friend class and should be considered as a part of ZoneEntry
+ * code. It manipulates directly ZoneEntry's data members, locks it and like
+ * that. Mostly eliminates C++ bad design of missing lambda functions.
+ */
+class ZoneEntry::ResolverCallback : public ResolverInterface::Callback {
+    public:
+        /// \short Constructor. Pass "this" zone entry
+        ResolverCallback(shared_ptr<ZoneEntry> entry) :
+            entry_(entry)
+        { }
+        /**
+         * \short It successfully received nameserver list.
+         *
+         * It fills the nameservers into the ZoneEntry whose callback this is.
+         * If there are in the hash table, it is used. If not, they are
+         * created. This might still fail, if the list is empty.
+         *
+         * It then calls process, to go trough the list of nameservers,
+         * examining them and seeing if some addresses are already there
+         * and to ask for the rest of them.
+         */
+        virtual void success(const shared_ptr<AbstractRRset>& answer) {
+            Lock lock(entry_->mutex_);
+            RdataIteratorPtr iterator(answer->getRdataIterator());
+            iterator->first();
+            // If there are no data
+            if (iterator->isLast()) {
+                failureInternal(answer->getTTL().getValue());
+                return;
+            } else {
+                /*
+                 * We store the nameservers we have currently (we might have
+                 * none, at startup, but when we time out and ask again, we
+                 * do), so we can just reuse them instead of looking them up in
+                 * the table or creating them.
+                 */
+                map<string, NameserverPtr> old;
+                BOOST_FOREACH(const NameserverPtr& ptr, entry_->nameservers_) {
+                    old[ptr->getName()] = ptr;
+                }
+                /*
+                 * List of original nameservers we did not ask for IP address
+                 * yet.
+                 */
+                set<NameserverPtr> old_not_asked;
+                old_not_asked.swap(entry_->nameservers_not_asked_);
+
+                // Once we have them put aside, remove the original set
+                // of nameservers from the entry
+                entry_->nameservers_.clear();
+                // And put the ones from the answer them, reusing if possible
+                for (; !iterator->isLast(); iterator->next()) {
+                    try {
+                        // Get the name from there
+                        Name ns_name(dynamic_cast<const rdata::generic::NS&>(
+                            iterator->getCurrent()).getNSName());
+                        // Try to find it in the old ones
+                        map<string, NameserverPtr>::iterator old_ns(old.find(
+                            ns_name.toText()));
+                        /*
+                         * We didn't have this nameserver before. So we just
+                         * look it up in the hash table or create it.
+                         */
+                        if (old_ns == old.end()) {
+                            // Look it up or create it
+                            string ns_name_str(ns_name.toText());
+                            pair<bool, NameserverPtr> from_hash(
+                                entry_->nameserver_table_->getOrAdd(HashKey(
+                                ns_name_str, entry_->class_code_), bind(
+                                newNs, &ns_name_str, &entry_->class_code_)));
+                            // Make it at the front of the list
+                            if (from_hash.first) {
+                                entry_->nameserver_lru_->add(from_hash.second);
+                            } else {
+                                entry_->nameserver_lru_->touch(
+                                    from_hash.second);
+                            }
+                            // And add it at last to the entry
+                            entry_->nameservers_.push_back(from_hash.second);
+                            entry_->nameservers_not_asked_.insert(
+                                from_hash.second);
+                        } else {
+                            // We had it before, reuse it
+                            entry_->nameservers_.push_back(old_ns->second);
+                            // Did we ask it already? If not, it is still not
+                            // asked (the one designing std interface must
+                            // have been mad)
+                            if (old_not_asked.find(old_ns->second) !=
+                                old_not_asked.end())
+                            {
+                                entry_->nameservers_not_asked_.insert(
+                                    old_ns->second);
+                            }
+                        }
+                    }
+                    // OK, we skip this one as it is not NS (log?)
+                    catch (bad_cast&) { }
+                }
+
+                // It is unbelievable, but we found no nameservers there
+                if (entry_->nameservers_.empty()) {
+                    // So we fail the same way as if we got empty list
+                    failureInternal(answer->getTTL().getValue());
+                    return;
+                } else {
+                    // Ok, we have them. So set us as ready, set our
+                    // expiration time and try to answer what we can, ask
+                    // if there's still someone to ask.
+                    entry_->setState(READY);
+                    entry_->expiry_ = answer->getTTL().getValue() + time(NULL);
+                    entry_->process(ADDR_REQ_MAX, NameserverPtr());
+                    return;
+                }
+            }
+        }
+        /// \short Failed to receive answer.
+        virtual void failure() {
+            failureInternal(300);
+        }
+    private:
+        /**
+         * \short Common function called when "it did not work"
+         *
+         * It marks the ZoneEntry as unreachable and processes callbacks (by
+         * calling process).
+         */
+        void failureInternal(time_t ttl) {
+            Lock lock(entry_->mutex_);
+            entry_->setState(UNREACHABLE);
+            entry_->expiry_ = ttl + time(NULL);
+            // Process all three callback lists and tell them KO
+            entry_->process(ADDR_REQ_MAX, NameserverPtr());
+        }
+        /// \short The entry we are callback of
+        shared_ptr<ZoneEntry> entry_;
+};
+
+void
+ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
+    Lock lock(mutex_);
+
+    bool ask(false);
+
+    // Look at expiration time
+    if (expiry_ && time(NULL) >= expiry_) {
+        setState(EXPIRED);
+    }
+
+    // We need to ask (again)
+    if (getState() == EXPIRED || getState() == NOT_ASKED) {
+        ask = true;
+    }
+
+    // We do not have the answer right away, just queue the callback
+    bool execute(!ask && getState() != IN_PROGRESS &&
+        callbacks_[family].empty());
+    callbacks_[family].push_back(callback);
+    if (execute) {
+        // Try to process it right away, store if not possible to handle
+        process(family, NameserverPtr());
+        return;
+    }
+
+    if (ask) {
+        setState(IN_PROGRESS);
+        // Our callback might be directly called from resolve, unlock now
+        QuestionPtr question(new Question(Name(name_), class_code_,
+            RRType::NS()));
+        shared_ptr<ResolverCallback> resolver_callback(
+            new ResolverCallback(shared_from_this()));
+        resolver_->resolve(question, resolver_callback);
+        return;
+    }
+}
+
+namespace {
+
+// This just moves items from one container to another
+template<class Container>
+void
+move(Container& into, Container& from) {
+    into.insert(into.end(), from.begin(), from.end());
+    from.clear();
+}
+
+// Update the address selector according to the RTTs
+//
+// Each address has a probability to be selected if multiple addresses are available
+// The weight factor is equal to 1/(rtt*rtt), then all the weight factors are normalized
+// to make the sum equal to 1.0
+void
+updateAddressSelector(std::vector<NameserverAddress>& addresses,
+    WeightedRandomIntegerGenerator& selector)
+{
+    vector<double> probabilities;
+    BOOST_FOREACH(NameserverAddress& address, addresses) {
+        uint32_t rtt = address.getAddressEntry().getRTT();
+        if(rtt == 0) {
+            isc_throw(RTTIsZero, "The RTT is 0");
+        }
+
+        if(rtt == AddressEntry::UNREACHABLE) {
+            probabilities.push_back(0);
+        } else {
+            probabilities.push_back(1.0/(rtt*rtt));
+        }
+    }
+    // Calculate the sum
+    double sum = accumulate(probabilities.begin(), probabilities.end(), 0.0);
+
+    if(sum != 0) {
+        // Normalize the probabilities to make the sum equal to 1.0
+        for(vector<double>::iterator it = probabilities.begin();
+                it != probabilities.end(); ++it){
+            (*it) /= sum;
+        }
+    } else if(probabilities.size() > 0){
+        // If all the nameservers are unreachable, the sum will be 0
+        // So give each server equal opportunity to be selected.
+        for(vector<double>::iterator it = probabilities.begin();
+                it != probabilities.end(); ++it){
+            (*it) = 1.0/probabilities.size();
+        }
+    }
+
+    selector.reset(probabilities);
+}
+
+}
+
+/**
+ * \short Sets given boolean to false when destroyed.
+ *
+ * This is hack eliminating C++ missing finally. We need to make sure
+ * the value gets set to false when we leave the function, so we use
+ * a Guard object, that sets it when it gets out of scope.
+ */
+class ZoneEntry::ProcessGuard {
+    public:
+        ProcessGuard(bool& guarded) :
+            guarded_(guarded)
+        { }
+        ~ ProcessGuard() {
+            guarded_ = false;
+        }
+    private:
+        bool& guarded_;
+};
+
+/**
+ * \short Callback from NameserverEntry to us.
+ *
+ * We registre object of this class whenever some ZoneEntry has a need to be
+ * notified of a change (received data) inside its NameserverEntry.
+ *
+ * This is part of the ZoneEntry code (not visible from outside, accessing
+ * private functions). It is here just because C++ does not know propper lambda
+ * functions.
+ */
+class ZoneEntry::NameserverCallback : public NameserverEntry::Callback {
+    public:
+        /**
+         * \short Constructor.
+         *
+         * \param entry The ZoneEntry to be notified.
+         * \param family For which address family this change is, so we
+         *     do not process all the nameserves and callbacks there.
+         */
+        NameserverCallback(shared_ptr<ZoneEntry> entry, AddressFamily family) :
+            entry_(entry),
+            family_(family)
+        { }
+        /**
+         * \short Callback method.
+         *
+         * This is called by NameserverEntry when the change happens.
+         * We just call process to go trough relevant nameservers and call
+         * any callbacks we can.
+         */
+        virtual void operator()(NameserverPtr ns) {
+            entry_->process(family_, ns);
+        }
+    private:
+        shared_ptr<ZoneEntry> entry_;
+        AddressFamily family_;
+};
+
+void
+ZoneEntry::dispatchFailures(AddressFamily family) {
+    // We extract all the callbacks
+    vector<CallbackPtr> callbacks;
+    if (family == ADDR_REQ_MAX) {
+        move(callbacks_[ANY_OK], callbacks_[V4_ONLY]);
+        move(callbacks_[ANY_OK], callbacks_[V6_ONLY]);
+        family = ANY_OK;
+    }
+    callbacks.swap(callbacks_[family]);
+    BOOST_FOREACH(const CallbackPtr& callback, callbacks) {
+        callback->unreachable();
+    }
+}
+
+void
+ZoneEntry::process(AddressFamily family,
+    const shared_ptr<NameserverEntry>& nameserver)
+{
+    Lock lock(mutex_);
+    switch (getState()) {
+        // These are not interesting, nothing to return now
+        case NOT_ASKED:
+        case IN_PROGRESS:
+        case EXPIRED:
+            break;
+        case UNREACHABLE: {
+            dispatchFailures(family);
+            // And we do nothing more now
+            break;
+        }
+        case READY:
+            if (family == ADDR_REQ_MAX) {
+                // Just process each one separately
+                // TODO Think this over, is it safe, to unlock in the middle?
+                process(ANY_OK, nameserver);
+                process(V4_ONLY, nameserver);
+                process(V6_ONLY, nameserver);
+            } else {
+                // Nothing to do anyway for this family, be dormant
+                if (callbacks_[family].empty()) {
+                    return;
+                }
+                /*
+                 * If we have multiple nameservers and more than 1 of them
+                 * is in the cache, we want to choose from all their addresses.
+                 * So we ensure this instance of process is the only one on
+                 * the stack. If not, we terminate and let the outernmost
+                 * one handle it when we return to it.
+                 *
+                 * If we didn't do it, one instance would call "resolve". If it
+                 * was from cache, it would imediatelly recurse back to another
+                 * process (trough the nameserver callback, etc), which would
+                 * take that only one nameserver and trigger all callbacks.
+                 * Only then would resolve terminate and we could ask for the
+                 * second nameserver. This way, we first receive all the
+                 * nameservers that are already in cache and trigger the
+                 * callbacks only then.
+                 *
+                 * However, this does not wait for external fetches of
+                 * nameserver addresses, as the callback is called after
+                 * process terminates. Therefore this waits only for filling
+                 * of the nameservers which we already have in cache.
+                 */
+                if (in_process_[family]) {
+                    return;
+                }
+                // Mark we are on the stack
+                ProcessGuard guard(in_process_[family]);
+                in_process_[family] = true;
+                // Variables to store the data to
+                NameserverEntry::AddressVector addresses;
+                NameserverVector to_ask;
+                bool pending(false);
+
+                // Pick info from the nameservers
+                BOOST_FOREACH(const NameserverPtr& ns, nameservers_) {
+                    Fetchable::State ns_state(ns->getAddresses(addresses,
+                        family, ns == nameserver));
+                    switch (ns_state) {
+                        case IN_PROGRESS:
+                            pending = true;
+                            // Someone asked it, but not us, we don't have
+                            // callback
+                            if (nameservers_not_asked_.find(ns) !=
+                                nameservers_not_asked_.end())
+                            {
+                                to_ask.push_back(ns);
+                            }
+                            break;
+                        case NOT_ASKED:
+                        case EXPIRED:
+                            to_ask.push_back(ns);
+                            break;
+                        case UNREACHABLE:
+                        case READY:
+                            // Not interested, but avoiding warning
+                            break;
+                    }
+                }
+
+                // We have someone to ask, so do it
+                if (!to_ask.empty()) {
+                    // We ask everything that makes sense now
+                    nameservers_not_asked_.clear();
+                    /*
+                     * TODO: Possible place for an optimisation. We now ask
+                     * everything we can. We should limit this to something like
+                     * 2 concurrent NS fetches (and fetch cache first, then
+                     * fetch the remote ones). But fetching everything right
+                     * away is simpler.
+                     */
+                    BOOST_FOREACH(const NameserverPtr& ns, to_ask) {
+                        // Put all 3 callbacks there. If we put just the
+                        // current family, it might not work due to missing
+                        // callback for different one.
+                        // If they recurse back to us (call directly), we kill
+                        // it by the in_process_
+                        insertCallback(ns, ADDR_REQ_MAX);
+                    }
+                    // Retry with all the data that might have arrived
+                    in_process_[family] = false;
+                    // We do not provide the callback again
+                    process(family, nameserver);
+                    // And be done
+                    return;
+                // We have some addresses to answer
+                } else if (!addresses.empty()) {
+                    // Prepare the selector of addresses
+                    // TODO: Think of a way how to keep it for a while
+                    //   (not update every time)
+                    updateAddressSelector(addresses, address_selector);
+
+                    // Extract the callbacks
+                    vector<CallbackPtr> to_execute;
+                    // FIXME: Think of a solution where we do not lose
+                    // any callbacks upon exception
+                    to_execute.swap(callbacks_[family]);
+
+                    // Run the callbacks
+                    BOOST_FOREACH(const CallbackPtr& callback, to_execute) {
+                        callback->success(addresses[address_selector()]);
+                    }
+                    return;
+                } else if (!pending) {
+                    dispatchFailures(family);
+                    return;
+                }
+            }
+            return;
+    }
+}
+
+void
+ZoneEntry::insertCallback(NameserverPtr ns, AddressFamily family) {
+    if (family == ADDR_REQ_MAX) {
+        insertCallback(ns, ANY_OK);
+        insertCallback(ns, V4_ONLY);
+        insertCallback(ns, V6_ONLY);
+    } else {
+        shared_ptr<NameserverCallback> callback(new NameserverCallback(
+            shared_from_this(), family));
+        ns->askIP(resolver_, callback, family);
+    }
+}
+
+}; // namespace nsas
+}; // namespace isc

+ 111 - 35
src/lib/nsas/zone_entry.h

@@ -19,19 +19,26 @@
 
 #include <string>
 #include <vector>
+#include <set>
 #include <boost/thread.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
 
-#include "rrset.h"
+#include <dns/rrset.h>
 
 #include "hash_key.h"
 #include "nsas_entry.h"
 #include "asiolink.h"
+#include "fetchable.h"
+#include "resolver_interface.h"
+#include "nsas_types.h"
+#include "random_number_generator.h"
 
 namespace isc {
 namespace nsas {
 
 class NameserverEntry;
+class AddressRequestCallback;
 
 /// \brief Zone Entry
 ///
@@ -41,59 +48,128 @@ class NameserverEntry;
 /// Although the interface is simple, the internal processing is fairly
 /// complicated, in that the class takes account of triggering fetches for
 /// addresses of nameservers when the address records expire.
+///
+/// It uses shared_from_this in its methods. It must live inside a shared_ptr.
 
-class ZoneEntry : public NsasEntry<ZoneEntry> {
+class ZoneEntry : public NsasEntry<ZoneEntry>, public Fetchable {
 public:
 
-    /// \brief Constructor where no NS records are supplied
-    ///
-    /// \param name Name of the zone
-    /// \param class_code Class of this zone (zones of different classes have
-    /// different objects.
-    ZoneEntry(const std::string& name, uint16_t class_code) :
-        name_(name), classCode_(class_code)
-    {}
-
-    /// \brief Constructor
-    ///
-    /// Creates a zone entry object with an RRset representing the nameservers,
-    /// plus possibly additional RRsets holding address information.
-    //ZoneEntry(isc::dns::AbstractRRset* nsrrset,
-    //       const std::vector<isc::dns::AbstractRRset*>& additional);
-
-    /// \brief Destructor
-    virtual ~ZoneEntry()
-    {}
+    /**
+     * \brief Constructor.
+     *
+     * It asks the resolver any needed questions to get the nameservers.
+     *
+     * \param resolver The resolver used to ask for IP addresses
+     * \param name Name of the zone
+     * \param class_code Class of this zone (zones of different classes have
+     *     different objects.
+     * \todo Move to cc file, include the lookup (if NSAS uses resolver for
+     *     everything)
+     */
+    ZoneEntry(boost::shared_ptr<ResolverInterface> resolver,
+        const std::string& name, const isc::dns::RRClass& class_code,
+        boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
+        boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru);
 
     /// \return Name of the zone
-    virtual std::string getName() const {
+    std::string getName() const {
         return name_;
     }
 
     /// \return Class of zone
-    virtual short getClass() const {
-        return classCode_;
+    const isc::dns::RRClass& getClass() const {
+        return class_code_;
     }
 
     /// \return Return Hash Key
     virtual HashKey hashKey() const {
-        return HashKey(name_, classCode_);
+        return HashKey(name_, class_code_);
     }
 
-    /// \brief Lookup Address
-    ///
-    /// Returns the address with the lowest RTT.
-    //virtual asiolink::IOAddress getAddress() const;
-
+    /**
+     * \short Put another callback inside.
+     *
+     * This callback is either executed right away, if it is possible,
+     * or queued for later.
+     *
+     * \param callback The callback itself.
+     * \param family Which address family is acceptable as an answer?
+     */
+    void addCallback(boost::shared_ptr<AddressRequestCallback>
+        callback, AddressFamily family);
+
+    /// \short Protected members, so they can be accessed by tests.
+    //@{
+protected:
+    // TODO Read-Write lock?
+    typedef boost::shared_ptr<NameserverEntry> NameserverPtr;
+    typedef std::vector<NameserverPtr> NameserverVector;
+    NameserverVector nameservers_; ///< Nameservers
+    // Which nameservers didn't have any of our callbacks yet
+    std::set<NameserverPtr> nameservers_not_asked_;
+    /*
+     * Callbacks. For each fimily type one vector, so we can process
+     * them separately.
+     */
+    std::vector<boost::shared_ptr<AddressRequestCallback> >
+        callbacks_[ADDR_REQ_MAX];
+    time_t          expiry_;    ///< Expiry time of this entry, 0 means not set
+    //}@
 private:
-    boost::mutex    mutex_;     ///< Mutex protecting this zone entry
+    mutable boost::recursive_mutex    mutex_;     ///< Mutex protecting this zone entry
     std::string     name_;      ///< Canonical zone name
-    uint16_t        classCode_; ///< Class code
-    std::vector<boost::shared_ptr<NameserverEntry> > nameservers_; ///< Nameservers
-    time_t          expiry_;    ///< Expiry time of this entry
+    isc::dns::RRClass        class_code_; ///< Class code
+    /**
+     * \short Process all the callbacks that can be processed
+     *
+     * The purpose of this funtion is to ask all nameservers for their IP
+     * addresses and execute all callbacks that can be executed. It is
+     * called whenever new callback appears and there's a chance it could
+     * be answered or when new information is available (list of nameservers,
+     * nameserver is unreachable or has an address).
+     * \param family Which is the interesting address family where the change
+     *     happened. ADDR_REQ_MAX means it could be any of them and it will
+     *     trigger processing of all callbacks no matter what their family
+     *     was.
+     * \param nameserver Pass a nameserver if the change was triggered by
+     *     the nameserver (if it wasn't triggered by a nameserver, pass empty
+     *     pointer). This one will be accepted even with 0 TTL, the information
+     *     just arrived and we are allowed to use it just now.
+     * \todo With the recursive locks now, we might want to simplify executing
+     *     callbacks (here and other functions as well);
+     */
+    void process(AddressFamily family,
+        const boost::shared_ptr<NameserverEntry>& nameserver);
+    // Resolver we use
+    boost::shared_ptr<ResolverInterface> resolver_;
+    // We store the nameserver table and lru, so we can look up when there's
+    // update
+    boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table_;
+    boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
+    // Resolver callback class, documentation with the class declaration
+    class ResolverCallback;
+    // It has direct access to us
+    friend class ResolverCallback;
+    // Guard class to eliminate missing finally
+    class ProcessGuard;
+    friend class ProcessGuard;
+    // Are we in the process method?
+    bool in_process_[ADDR_REQ_MAX];
+    // Callback from nameserver entry (documented with the class)
+    class NameserverCallback;
+    // And it can get into our internals as well (call process)
+    friend class NameserverCallback;
+    // This dispatches callbacks of given family with failures
+    void dispatchFailures(AddressFamily family);
+    // Put a callback into the nameserver entry. Same ADDR_REQ_MAX means for
+    // all families
+    void insertCallback(NameserverPtr nameserver, AddressFamily family);
+    // A random generator for this zone entry
+    // TODO: A more global one? Per thread one?
+    WeightedRandomIntegerGenerator address_selector;
 };
 
 } // namespace nsas
 } // namespace isc
- 
+
 #endif // __ZONE_ENTRY_H