Browse Source

Oops: Forgot to explicitly add some files in the recent merge.

git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@2385 e5f2f494-b856-4b98-b285-d166d9295462
Evan Hunt 15 years ago
parent
commit
d264ba4813
3 changed files with 931 additions and 0 deletions
  1. 364 0
      src/lib/datasrc/cache.cc
  2. 225 0
      src/lib/datasrc/cache.h
  3. 342 0
      src/lib/datasrc/tests/cache_unittest.cc

+ 364 - 0
src/lib/datasrc/cache.cc

@@ -0,0 +1,364 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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 <stdint.h>
+
+#include <map>
+
+#include <dns/question.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+#include <list>
+
+#include <datasrc/cache.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+/// \brief A \c CacheEntry object contains the data stored with
+/// each \c CacheNode: a pointer to the cached RRset (empty in
+/// the case of a negative cache entry), and a copy of the
+/// query-response flags that were returned when the RRset
+/// was originally looked up in the low-level data source.
+class CacheEntry {
+private:
+    /// The copy constructor and the assignment operator are intentionally
+    /// defined as private.
+    CacheEntry(const CacheEntry& source);
+    CacheEntry& operator=(const CacheEntry& source);
+
+public:
+    CacheEntry(RRsetPtr r, uint32_t f) : rrset(r), flags(f) {};
+
+    RRsetPtr rrset;
+    uint32_t flags;
+};
+
+typedef boost::shared_ptr<CacheEntry> CacheEntryPtr;
+
+/// \brief A \c CacheNode is a node in the \c HotCache LRU queue.  It
+/// contains a pointer to a \c CacheEntry, a reference to the \c Question
+/// that we are answering, a lifespan during which this entry remains
+/// valid, and pointers to the next and previous entries in the list.
+class CacheNode {
+private:
+    /// \name Constructors and Assignment Operator
+    ///
+    /// Note: The copy constructor and the assignment operator are intentionally
+    /// defined as private.
+    //@{
+    CacheNode(const CacheNode& source);
+    CacheNode& operator=(const CacheNode& source);
+
+public:
+    /// \brief Constructor for positive cache entry.
+    ///
+    /// \param rrset The \c RRset to cache.
+    /// \param flags The query response flags returned from the low-level
+    /// data source when this \c RRset was looked up.
+    /// \param lifespan How long the cache node is to be considered valid.
+    CacheNode(const RRsetPtr rrset, uint32_t flags, time_t lifespan);
+
+    /// \brief Constructor for negative cache entry.
+    ///
+    /// \param name Query name
+    /// \param rrclass Query class
+    /// \param rrtype Query type
+    /// \param flags Query response flags returned from the low-level
+    /// data source, indicating why this lookup failed (name not found,
+    /// type not found, etc).
+    /// \param lifespan How long the cache node is to be considered valid.
+    CacheNode(const Name& name,
+              const RRClass& rrclass,
+              const RRType& rrtype,
+              uint32_t flags,
+              time_t lifespan);
+    //@}
+
+    /// \name Getter and Setter Methods
+    //@{
+    /// \brief Returns a pointer to the cached RRset (or an empty
+    /// RRsetPtr for negative cache entries).
+
+    /// \return \c RRsetPtr
+    RRsetPtr getRRset() const { return (entry->rrset); }
+
+    /// \brief Returns the query response flags associated with the data.
+    ///
+    /// \return \c uint32_t
+    uint32_t getFlags() const { return (entry->flags); }
+
+    /// \brief Is this record still valid?
+    ///
+    /// \return True if the expiration time has not yet passed,
+    /// or false if it has.
+    bool isValid() const;
+    //@}
+
+    // An iterator referencing this entry in the LRU list. This
+    // permits unit-time removal using list::erase().
+    list<CacheNodePtr>::iterator lru_entry_;
+
+    /// The \c Question (name/rrclass/rrtype) answered by this cache node
+    const isc::dns::Question question;
+
+private:
+    // The cached RRset data
+    CacheEntryPtr entry;
+
+    // When this record should be discarded
+    time_t expiry;
+};
+
+// CacheNode constructor for a positive cache entry
+CacheNode::CacheNode(const RRsetPtr rrset, const uint32_t flags,
+                     const time_t lifespan) :
+    question(Question(rrset->getName(), rrset->getClass(), rrset->getType()))
+{
+    const time_t now = time(NULL);
+    expiry = now + lifespan;
+
+    entry = CacheEntryPtr(new CacheEntry(rrset, flags));
+}
+
+// CacheNode constructor for a negative cache entry
+CacheNode::CacheNode(const Name& name,
+                     const RRClass& rrclass,
+                     const RRType& rrtype,
+                     const uint32_t flags,
+                     const time_t lifespan) :
+    question(Question(name, rrclass, rrtype))
+{
+    const time_t now = time(NULL);
+    expiry = now + lifespan;
+
+    entry = CacheEntryPtr(new CacheEntry(RRsetPtr(), flags));
+}
+// Returns true if the node has not yet expired.
+bool
+CacheNode::isValid() const {
+    const time_t now = time(NULL);
+    return (now < expiry);
+}
+
+/// This class abstracts the implementation details for \c HotCache.
+///
+/// Each node inserted into the cache is placed at the head of a
+/// doubly-linked list.  Whenever that node is retrieved from the cache,
+/// it is again moved to the head of the list.  When the configured
+/// number of slots in the cache has been exceeded, the least recently
+/// used nodes will be removed from the tail of the list.
+///
+/// A pointer to each cache node is also placed in a \c std::map object,
+/// keyed by \c isc::dns::Question.  This allows retrieval of data in
+/// (usually) logarithmic time.  (Possible TODO item: replace this with a
+/// hash table instead.)
+class HotCacheImpl {
+public:
+    HotCacheImpl(int slots, bool enabled);
+
+    // The LRU list
+    list<CacheNodePtr> lru_;
+
+    // Flag to indicate whether the cache is enabled
+    bool enabled_;
+
+    // The number of available slots in the LRU list.  (If zero,
+    // then the list is unbounded; otherwise, items are removed
+    // from the tail of the list whenever it grows past slots_.)
+    int slots_;
+
+    // The number of items currently in the list.
+    int count_;
+
+    // Map from query tuple to cache node pointer, allowing fast retrieval
+    // of data without a linear search of the LRU list
+    std::map<Question, CacheNodePtr> map_;
+
+    // Move a node to the front of the LRU list.
+    void promote(CacheNodePtr node);
+
+    // Remove a node from the cache.
+    void remove(ConstCacheNodePtr node);
+
+    // Insert a node into the cache (called by both cache() and ncache())
+    void insert(CacheNodePtr node);
+};
+
+// HotCacheImpl constructor
+HotCacheImpl::HotCacheImpl(int slots, bool enabled) :
+    enabled_(enabled), slots_(slots), count_(0)
+{}
+
+// Insert a cache node into the cache
+inline void
+HotCacheImpl::insert(const CacheNodePtr node) {
+    std::map<Question, CacheNodePtr>::const_iterator iter;
+    iter = map_.find(node->question);
+    if (iter != map_.end()) {
+        CacheNodePtr old = iter->second;
+        if (old && old->isValid()) {
+            remove(old);
+        }
+    }
+
+    lru_.push_front(node);
+    node->lru_entry_ = lru_.begin();
+
+    map_[node->question] = node;
+    ++count_;
+
+    if (slots_ != 0 && count_ > slots_) {
+        remove(lru_.back());
+    }
+}
+
+// Promote a node to the head of the LRU list
+void
+HotCacheImpl::promote(CacheNodePtr node) {
+    if (!node) {
+        return;
+    }
+    if (node->lru_entry_ == lru_.begin()) {
+        return;
+    }
+    lru_.erase(node->lru_entry_);
+    lru_.push_front(node);
+    node->lru_entry_ = lru_.begin();
+}
+
+// Remove a node from the LRU list and the map
+void
+HotCacheImpl::remove(ConstCacheNodePtr node) {
+    lru_.erase(node->lru_entry_);
+    map_.erase(node->question);
+    --count_;
+}
+
+// HotCache constructor
+HotCache::HotCache(const int slots) {
+    impl_ = new HotCacheImpl(slots, true);
+}
+
+// HotCache destructor
+HotCache::~HotCache() {
+    delete impl_;
+}
+
+// Add a positive entry to the cache
+void
+HotCache::addPositive(RRsetPtr rrset, const uint32_t flags,
+                      const time_t lifespan)
+{
+    if (!impl_->enabled_) {
+        return;
+    }
+
+    impl_->insert(CacheNodePtr(new CacheNode(rrset, flags, lifespan)));
+}
+
+// Add a negative entry to the cache
+void
+HotCache::addNegative(const Name& name, const RRClass &rrclass,
+                      const RRType& rrtype, const uint32_t flags,
+                      const time_t lifespan)
+{
+    if (!impl_->enabled_) {
+        return;
+    }
+
+    if (rrtype == RRType::ANY() || rrclass == RRClass::ANY()) {
+        return;
+    }
+
+    impl_->insert(CacheNodePtr(new CacheNode(name, rrclass, rrtype,
+                                             flags, lifespan)));
+}
+
+// Try to retrieve an entry from the cache, returning true if
+// it was found and valid.
+bool
+HotCache::retrieve(const Name& n, const RRClass& c, const RRType& t,
+                   RRsetPtr& rrset, uint32_t& flags)
+{
+    if (!impl_->enabled_) {
+        return (false);
+    }
+
+    std::map<Question, CacheNodePtr>::const_iterator iter;
+    iter = impl_->map_.find(Question(n, c, t));
+    if (iter == impl_->map_.end()) {
+        return (false);
+    }
+
+    CacheNodePtr node = iter->second;
+
+    if (node->isValid()) {
+        impl_->promote(node);
+        rrset = node->getRRset();
+        flags = node->getFlags();
+        return (true);
+    }
+
+    impl_->remove(node);
+    return (false);
+}
+
+// Set the number of slots in the cache.
+void
+HotCache::setSlots(const int slots) {
+    impl_->slots_ = slots;
+
+    if (!impl_->enabled_) {
+        return;
+    }
+
+    while (impl_->slots_ != 0 && impl_->count_ > impl_->slots_) {
+        impl_->remove(impl_->lru_.back());
+    }
+}
+
+// Return the number of slots in the cache
+int
+HotCache::getSlots() const {
+    return (impl_->slots_);
+}
+
+/// Enable or disable the cache
+void
+HotCache::setEnabled(const bool e) {
+    impl_->enabled_ = e;
+}
+
+/// Indicate whether the cache is enabled
+bool
+HotCache::getEnabled() const {
+    return (impl_->enabled_);
+}
+
+// Return the number of entries in the cache
+int
+HotCache::getCount() const {
+    return (impl_->count_);
+}
+
+}
+}

+ 225 - 0
src/lib/datasrc/cache.h

@@ -0,0 +1,225 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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 __CACHE_H
+#define __CACHE_H
+
+#include <time.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+class RRType;
+}
+
+namespace datasrc {
+
+class CacheNode;
+typedef boost::shared_ptr<CacheNode> CacheNodePtr;
+typedef boost::shared_ptr<const CacheNode> ConstCacheNodePtr;
+
+class HotCacheImpl;
+
+/// \brief A \c HotCache is a hot-spot cache for one or more data sources.
+///
+/// A \c HotCache must be instantiated prior to creating a \c Query.
+/// The same instance should be passed to the constructor for all queries,
+/// so that all of them will be using the same cache.
+///
+/// The cache may contain positive or negative entries, indicating
+/// whether the data does or does not exist in the underlying data
+/// source.  Entries have a fixed and limited lifespan (currently
+/// set to 30 seconds, see LIFESPAN_ below).  If a cache entry is
+/// found which has exceeded its lifespan, it will not be returned
+/// to the caller--exactly as if it had not been found.
+/// 
+/// The current 30 second cache entry lifespan is experimental.  A longer
+/// lifespan would improve performance somewhat; however, longer-lived
+/// cache entries are more likely to be incorrect in the event that
+/// the underlying data source had been updated.  Depending on the
+/// frequency of queries and the frequency of updates, longer or
+/// shorter lifespans may be desirable -- it's even possible
+/// we may want the lifespan to be set differently depending on
+/// the zone or the data source (i.e., with an infinite lifespan
+/// for cached data from a static data source).  Additional benchmarking
+/// and analysis will be needed for this.
+/// 
+/// The cache may be configured with a number of available slots for
+/// for entries.  When set to a nonzero value, no more than that number
+/// of entries can exist in the cache.  If more entries are inserted,
+/// old entries will be dropped in "least recently used" order.  If
+/// set to zero, the cache size is unlimited.  The current default is
+/// based on one thousand queries per second, times the number of seconds
+/// in the cache lifespan: 30,000 slots.
+///
+/// Notes to developers: The current implementation of HotCache uses
+/// a std::map (keyed by isc::dns::Question) to locate nodes, so access
+/// will generally be in O(log n) time.  (XXX: This might be faster if a
+/// hash table were used instead.)
+///
+/// A linked list is also maintained to keep track of recent accesses
+/// to cache entries; each time an entry is accessed, it is moved to the
+/// head of the list; when entries need to be removed, they are taken
+/// from the tail of the list.  This operation is not locked.  BIND 10
+/// does not currently use threads, but if it ever does (or if libdatasrc
+/// is ever used by a threaded application), this will need to be
+//revisited.
+class HotCache {
+private:
+    /// \name Static definitions
+    //@{
+    /// \brief Default validity period for cache entries
+    static const int LIFESPAN_ = 30;
+
+    /// \brief Default number of slots in cache
+    static const int SLOTS_ = 1000 * LIFESPAN_;
+    //@}
+
+    /// \name Constructors, Assignment Operator and Destructor.
+    ///
+    /// Note: The copy constructor and the assignment operator are intentionally
+    /// defined as private.
+    //@{
+    HotCache(const HotCache& source);
+    HotCache& operator=(const HotCache& source);
+
+public:
+    /// \brief Constructor for HotCache
+    ///
+    /// \param slots The number of slots available in the cache.
+    HotCache(const int slots = SLOTS_);
+
+    /// \brief Destructor for HotCache
+    ~HotCache();
+    //@}
+
+    /// \name Cache Manipulation Methods
+    //@{
+    /// \brief Enter a positive cache entry.
+    ///
+    /// If an entry already exists in the cache which matches the
+    /// name/class/type of the RRset being cached, then the old entry
+    /// is removed before the the new one is inserted.  (XXX: This is
+    /// currently slightly inefficient; it would be quicker to keep the
+    /// existing node and simply update the rrset, flags, and lifespan.)
+    ///
+    /// \param rrset The \c RRset to cache.
+    /// \param flags The query response flags returned from the low-level
+    /// data source when this \c RRset was looked up.
+    /// \param lifespan How long the cache node is to be considered valid;
+    /// defaulting to 30 seconds.
+    void addPositive(isc::dns::RRsetPtr rrset,
+                     uint32_t flags,
+                     time_t lifespan = LIFESPAN_);
+
+    /// \brief Enter a negative cache entry.
+    ///
+    /// In the case of a negative cache entry there is no \c RRset to
+    /// cache, so instead a null \c RRsetPtr will be stored.  Since the
+    /// name, class, and type cannot be retrieved from an \c RRset, they
+    /// must be specified in the parameters.
+    ///
+    /// If an entry already exists in the cache which matches the
+    /// specified name/class/type, then the old entry is removed
+    /// before the the new one is inserted.  (XXX: As noted in the comments
+    /// for addPositive(), this is currently slightly inefficient.)
+    /// 
+    /// \param name Query name
+    /// \param rrclass Query class
+    /// \param rrtype Query type
+    /// \param flags Query response flags returned from the low-level
+    /// data source, indicating why this lookup failed (name not found,
+    /// type not found, etc).
+    /// \param lifespan How long the cache node is to be considered valid;
+    /// defaulting to 30 seconds.
+    ///
+    /// Note: 'rrclass' and 'rrtype' must refer to a specific class and
+    /// type; it is not meaningful to cache type or class ANY.  Currently,
+    /// this condition is silently ignored.
+    void addNegative(const isc::dns::Name& name,
+                     const isc::dns::RRClass& rrclass,
+                     const isc::dns::RRType& rrtype,
+                     uint32_t flags,
+                     time_t lifespan = LIFESPAN_);
+
+    /// \brief Retrieve (and promote) a record from the cache
+    ///
+    /// Retrieves a record from the cache matching the given 
+    /// query-tuple.  Returns true if one is found.  If it is a
+    /// posiitve cache entry, then 'rrset' is set to the cached
+    /// RRset.  For both positive and negative cache entries, 'flags'
+    /// is set to the query response flags.  The cache entry is 
+    /// then promoted to the head of the LRU queue.  (NOTE: Because
+    /// of this, "retrieve" cannot be implemented as a const method.)
+    ///
+    /// \param name The query name
+    /// \param rrclass The query class
+    /// \param rrtype The query type
+    /// \param rrset Returns the RRset found, if any, to the caller
+    /// \param flags Returns the flags, if any, to the caller
+    ///
+    /// \return \c bool, true if data was found in the cache, false if not.
+    bool retrieve(const isc::dns::Name& qname,
+                  const isc::dns::RRClass& qclass,
+                  const isc::dns::RRType& qtype,
+                  isc::dns::RRsetPtr& rrset,
+                  uint32_t& flags);
+    //@}
+
+    /// \name Getter and Setter Methods
+    //@{
+    /// \brief Sets the number of slots in the cache.
+    ///
+    /// If slots is set to zero, the cache can grow without any imposed
+    /// limit.  If slots is to set a lower number than the cache currently
+    /// contains, then the least recently used records will be purged from
+    /// the cache until the total number of items in the cache equals slots.
+    void setSlots(int slots);
+
+    /// \brief Returns the number of slots in the cache.
+    int getSlots() const;
+
+    /// \brief Enable or disable the cache
+    void setEnabled(bool e);
+
+    /// \brief Indicate whether the cache is enabled
+    bool getEnabled() const;
+
+    /// \brief Returns the number of nodes currently stored in the cache.
+    ///
+    /// Note that this doesn't indicate how many nodes are still valid;
+    /// some may have expired.
+    int getCount() const;
+    //@}
+
+private:
+    /// \brief Hidden implementation details
+    HotCacheImpl* impl_;
+};
+
+}
+}
+
+#endif
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 342 - 0
src/lib/datasrc/tests/cache_unittest.cc

@@ -0,0 +1,342 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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 <stdexcept>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rrset.h>
+
+#include <datasrc/cache.h>
+#include <datasrc/data_source.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc;
+
+namespace {
+class CacheTest : public ::testing::Test {
+protected:
+    CacheTest() : test_name("test.example.com"),
+                  test_nsname("ns.example.com"),
+                  test_ch("example.com")
+    {
+        RRsetPtr a(new RRset(test_name, RRClass::IN(), RRType::A(),
+                             RRTTL(3600)));
+        a->addRdata(in::A("192.0.2.1"));
+        a->addRdata(in::A("192.0.2.2"));
+
+        RRsetPtr b(new RRset(test_nsname, RRClass::IN(), RRType::NS(),
+                             RRTTL(86400)));
+        RRsetPtr c(new RRset(test_ch, RRClass::CH(), RRType::TXT(),
+                             RRTTL(0)));
+        c->addRdata(generic::TXT("Text record"));
+
+        cache.setSlots(5);
+
+        cache.addPositive(a, 1, 30);
+        cache.addPositive(b, 2, 30);
+        cache.addPositive(c, 4, 30);
+    }
+
+    Name test_name;
+    Name test_nsname;
+    Name test_ch;
+
+    HotCache cache;
+};
+
+class TestRRset : public RRset {
+public:
+    TestRRset(const Name& name, int& counter) :
+        RRset(name, RRClass::IN(), RRType::A(), RRTTL(3600)),
+        counter_(counter)
+    {
+        ++counter_;
+    }
+    ~TestRRset() {
+        --counter_;
+    }
+    int& counter_;
+};
+
+// make sure any remaining cache entries are purged on destruction of the
+// cache.
+TEST_F(CacheTest, cleanup) {
+    HotCache* local_cache(new HotCache);
+    int num_rrsets = 0;
+
+    local_cache->addPositive(RRsetPtr(new TestRRset(Name("example.com"),
+                                                    num_rrsets)), 0, 10);
+    local_cache->addPositive(RRsetPtr(new TestRRset(Name("example.org"),
+                                                    num_rrsets)), 0, 10);
+
+    EXPECT_EQ(2, num_rrsets);
+    delete local_cache;
+    EXPECT_EQ(0, num_rrsets);
+}
+
+TEST_F(CacheTest, slots) {
+    EXPECT_EQ(5, cache.getSlots());
+    EXPECT_EQ(3, cache.getCount());
+}
+
+TEST_F(CacheTest, insert) {
+    RRsetPtr aaaa(new RRset(Name("foo"), RRClass::IN(), RRType::AAAA(),
+                            RRTTL(0)));
+    aaaa->addRdata(in::AAAA("2001:db8:3:bb::5"));
+    cache.addPositive(aaaa, 0, 4);
+
+    EXPECT_EQ(4, cache.getCount());
+
+    RRsetPtr r;
+    uint32_t f;
+    bool hit = cache.retrieve(Name("foo"), RRClass::IN(),
+                                RRType::AAAA(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(aaaa, r);
+}
+
+TEST_F(CacheTest, retrieveOK) {
+    bool hit;
+    RRsetPtr r;
+    uint32_t f;
+
+    // Test repeatedly to ensure that all records remain accessible
+    // even after being promoted to the top of the cache
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_name, r->getName());
+    EXPECT_EQ(RRClass::IN(), r->getClass());
+    EXPECT_EQ(RRType::A(), r->getType());
+
+    hit = cache.retrieve(test_nsname, RRClass::IN(), RRType::NS(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_nsname, r->getName());
+    EXPECT_EQ(RRClass::IN(), r->getClass());
+    EXPECT_EQ(RRType::NS(), r->getType());
+
+    hit = cache.retrieve(test_ch, RRClass::CH(), RRType::TXT(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_ch, r->getName());
+    EXPECT_EQ(RRClass::CH(), r->getClass());
+    EXPECT_EQ(RRType::TXT(), r->getType());
+    
+    hit = cache.retrieve(test_nsname, RRClass::IN(), RRType::NS(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_nsname, r->getName());
+    EXPECT_EQ(RRClass::IN(), r->getClass());
+    EXPECT_EQ(RRType::NS(), r->getType());
+
+    hit = cache.retrieve(test_ch, RRClass::CH(), RRType::TXT(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_ch, r->getName());
+    EXPECT_EQ(RRClass::CH(), r->getClass());
+    EXPECT_EQ(RRType::TXT(), r->getType());
+
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_name, r->getName());
+    EXPECT_EQ(RRClass::IN(), r->getClass());
+    EXPECT_EQ(RRType::A(), r->getType());
+
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(test_name, r->getName());
+    EXPECT_EQ(RRClass::IN(), r->getClass());
+    EXPECT_EQ(RRType::A(), r->getType());
+};
+
+TEST_F(CacheTest, flags) {
+    bool hit;
+    RRsetPtr r;
+    uint32_t f;
+
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_TRUE(r);
+    EXPECT_EQ(DataSrc::REFERRAL, f);
+
+    hit = cache.retrieve(test_nsname, RRClass::IN(), RRType::NS(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_TRUE(r);
+    EXPECT_EQ(DataSrc::CNAME_FOUND, f);
+
+    hit = cache.retrieve(test_ch, RRClass::CH(), RRType::TXT(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_TRUE(r);
+    EXPECT_EQ(DataSrc::NAME_NOT_FOUND, f);
+}
+
+TEST_F(CacheTest, retrieveFail) {
+    bool hit;
+    RRsetPtr r;
+    uint32_t f;
+
+    hit = cache.retrieve(Name("fake"), RRClass::IN(), RRType::A(), r, f);
+    EXPECT_FALSE(hit);
+
+    hit = cache.retrieve(test_name, RRClass::CH(), RRType::A(), r, f);
+    EXPECT_FALSE(hit);
+
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::DNSKEY(), r, f);
+    EXPECT_FALSE(hit);
+}
+
+TEST_F(CacheTest, expire) {
+    // Insert "foo" with a duration of 2 seconds; sleep 3.  The
+    // record should not be returned from the cache even though it's
+    // at the top of the cache.
+    RRsetPtr aaaa(new RRset(Name("foo"), RRClass::IN(), RRType::AAAA(),
+                            RRTTL(0)));
+    aaaa->addRdata(in::AAAA("2001:db8:3:bb::5"));
+    cache.addPositive(aaaa, 0, 2);
+
+    sleep(3);
+
+    RRsetPtr r;
+    uint32_t f;
+    bool hit = cache.retrieve(Name("foo"), RRClass::IN(), RRType::AAAA(), r, f);
+    EXPECT_FALSE(hit);
+};
+
+TEST_F(CacheTest, LRU) {
+    // Retrieve a record, cache four new records; with five slots
+    // in the LRU queue this should remove all the previous records
+    // except the last one retreived.
+    bool hit;
+    RRsetPtr r;
+    uint32_t f;
+
+    hit = cache.retrieve(test_nsname, RRClass::IN(), RRType::NS(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_EQ(3, cache.getCount());
+
+    RRsetPtr one(new RRset(Name("one"), RRClass::IN(), RRType::TXT(),
+                           RRTTL(0)));
+    one->addRdata(generic::TXT("one"));
+    cache.addPositive(one, 0, 30);
+    EXPECT_EQ(4, cache.getCount());
+
+    RRsetPtr two(new RRset(Name("two"), RRClass::IN(), RRType::TXT(),
+                           RRTTL(0)));
+    two->addRdata(generic::TXT("two"));
+    cache.addPositive(two, 0, 30);
+    EXPECT_EQ(5, cache.getCount());
+
+    RRsetPtr three(new RRset(Name("three"), RRClass::IN(), RRType::TXT(),
+                             RRTTL(0)));
+    three->addRdata(generic::TXT("three"));
+    cache.addPositive(three, 0, 30);
+    EXPECT_EQ(5, cache.getCount());
+
+    RRsetPtr four(new RRset(Name("four"), RRClass::IN(), RRType::TXT(),
+                            RRTTL(0)));
+    four->addRdata(generic::TXT("four"));
+    cache.addPositive(four, 0, 30);
+    EXPECT_EQ(5, cache.getCount());
+
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_FALSE(hit);
+
+    hit = cache.retrieve(test_nsname, RRClass::IN(), RRType::NS(), r, f);
+    EXPECT_TRUE(hit);
+
+    hit = cache.retrieve(test_ch, RRClass::CH(), RRType::TXT(), r, f);
+    EXPECT_FALSE(hit);
+}
+
+TEST_F(CacheTest, ncache) {
+    Name missing("missing.example.com");
+    cache.addNegative(missing, RRClass::IN(), RRType::DNSKEY(), 8, 30);
+
+    RRsetPtr r;
+    uint32_t f;
+    bool hit = cache.retrieve(missing, RRClass::IN(), RRType::DNSKEY(), r, f);
+
+    EXPECT_TRUE(hit);
+    EXPECT_FALSE(r);
+    EXPECT_EQ(DataSrc::TYPE_NOT_FOUND, f);
+}
+
+TEST_F(CacheTest, overwrite) {
+    EXPECT_EQ(3, cache.getCount());
+
+    RRsetPtr a(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(300)));
+    a->addRdata(in::A("192.0.2.100"));
+
+    EXPECT_NO_THROW(cache.addPositive(a, 16, 30));
+    EXPECT_EQ(3, cache.getCount());
+
+    RRsetPtr r;
+    uint32_t f;
+    bool hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_TRUE(r);
+    EXPECT_EQ(16, f);
+
+    EXPECT_NO_THROW(cache.addNegative(test_name, RRClass::IN(), RRType::A(), 1, 30));
+    EXPECT_EQ(3, cache.getCount());
+
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+    EXPECT_FALSE(r);
+    EXPECT_EQ(DataSrc::REFERRAL, f);
+}
+
+TEST_F(CacheTest, reduceSlots) {
+    EXPECT_EQ(3, cache.getCount());
+    cache.setSlots(2);
+    EXPECT_EQ(2, cache.getCount());
+    cache.setSlots(1);
+    EXPECT_EQ(1, cache.getCount());
+    cache.setSlots(0);
+    EXPECT_EQ(1, cache.getCount());
+}
+
+TEST_F(CacheTest, setEnabled) {
+    cache.setEnabled(false);
+    EXPECT_FALSE(cache.getEnabled());
+    cache.setEnabled(true);
+    EXPECT_TRUE(cache.getEnabled());
+}
+
+TEST_F(CacheTest, disabled) {
+    bool hit;
+    RRsetPtr r;
+    uint32_t f;
+
+    cache.setEnabled(false);
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_FALSE(hit);
+
+    cache.setEnabled(true);
+    hit = cache.retrieve(test_name, RRClass::IN(), RRType::A(), r, f);
+    EXPECT_TRUE(hit);
+
+    EXPECT_EQ(test_name, r->getName());
+    EXPECT_EQ(RRClass::IN(), r->getClass());
+    EXPECT_EQ(RRType::A(), r->getType());
+}
+
+}