For details, see @ref isc::dhcp::CqlConnection::openDatabase().
- @section dhcpdb-host Host Backends.
+ @section dhcpdb-host Host Backends
Host backends (known also as host data sources) are similar to lease
backends with a few differences:
is a premium feature) which avoids to lookup slow databases.
For subnet ID and identifier negative caching is optionally supported.
+ @subsection dhcpdb-caching Caching
+
+ Some of these considerations apply to lease backends too but only
+ the host caching was analyzed and implemented.
+
+ Caching divides into two parts, positive and negative caching, and
+ its support is implemented at two places, a cache backend and inside
+ the host manager, i.e. the entity calling backends in sequence
+ providing the result of lookups to allocation engines.
+
+ The idea of positive caching is simple: when a value not in the
+ cache in returned by a database, this value is added to the cache
+ so the next time it will be available without calling and waiting
+ for the database.
+
+ This cannot be extended to lookups returning a collection because
+ they are supposed to collect and append results from all backends.
+ If you replace append by merge you avoid duplicate items in the
+ result but still get no benefit from caching. So in general a cache
+ backend should simply return nothing for these lookups.
+
+ Add (or any operation which can fail) has to wait that all backends
+ are called and possibly one fails before the new entry being cached.
+ Del is simpler: the cache backend processes it but always returns
+ false so the backend holding it if any is called.
+
+ Negative caching consists into adding fake entries indicating that
+ a particular host does not exists. As no host constructor allows
+ a host object without an identifier or with an empty identifier,
+ negative caching applies only to by identifier lookups. This is
+ no a problem because out-of-pools provides a clearer and simpler
+ to implement performance benefit than by address negative caching.
+ Note that by identifier negative caching can be critical for
+ performance because the non-existence is the worst case for lookups.
+
+ Negative cache entries should be easily identified (current
+ implementation uses the negative_ flag member in @c host class)
+ so all lookups returning at most one entry can (in fact have to)
+ return a null pointer when they get a negative cache entry.
+ Note this is for all such lookups, not only by identifier lookups,
+ to allow to negative cached entries with any value, for instance
+ with a IP address.
+
+ There is no direct and simple way to support negative caching
+ for collection lookups so again cache backends should return nothing
+ for these lookups which have not to filter out negative cached entries
+ from result.
+
+ Negative caching can be performed by the host manager: when a by
+ identifier lookup returns a null pointer, a fake entry with lookup
+ parameters and the negative cache mark is inserted into the cache.
+ Note this leads to negative cache entries without IP reservations,
+ this property should not be used because it limits negative cache
+ addition to only be performed by the host manager.
+
*/
.arg(subnet_id)
.arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
.arg(duid ? duid->toText() : "(duid)");
- for (auto it = alternate_sources_.begin();
- !host && // stop at first found
- it != alternate_sources_.end(); ++it) {
+ for (auto source : alternate_sources_) {
if (hwaddr) {
- host = (*it)->get4(subnet_id, hwaddr, DuidPtr());
+ host = source->get4(subnet_id, hwaddr, DuidPtr());
}
if (!host && duid) {
- host = (*it)->get4(subnet_id, HWAddrPtr(), duid);
+ host = source->get4(subnet_id, HWAddrPtr(), duid);
+ }
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
}
- if (host && cache_ptr_ && (it != alternate_sources_.begin())) {
+ if (host && (source != cache_ptr_)) {
cache(host);
}
+ if (host) {
+ return (host);
+ }
}
- if (host && host->getNegative()) {
- return (ConstHostPtr());
- }
- return (host);
+ return (ConstHostPtr());
}
ConstHostPtr
// Try to find a host in each configured backend. We return as soon
// as we find first hit.
- for (auto it = alternate_sources_.begin();
- it != alternate_sources_.end(); ++it) {
- host = (*it)->get4(subnet_id, identifier_type,
+ for (auto source : alternate_sources_) {
+ host = source->get4(subnet_id, identifier_type,
identifier_begin, identifier_len);
if (host) {
.arg(Host::getIdentifierAsText(identifier_type,
identifier_begin,
identifier_len))
- .arg((*it)->getType())
+ .arg(source->getType())
.arg(host->toText());
- if (cache_ptr_ && (it != alternate_sources_.begin())) {
+ if (source != cache_ptr_) {
cache(host);
}
return (host);
HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4)
.arg(subnet_id)
.arg(address.toText());
- for (auto it = alternate_sources_.begin();
- !host && // stop at first found
- it != alternate_sources_.end(); ++it) {
- host = (*it)->get4(subnet_id, address);
- if (host && cache_ptr_ && (it != alternate_sources_.begin())) {
+ for (auto source : alternate_sources_) {
+ host = source->get4(subnet_id, address);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ }
+ if (host && source != cache_ptr_) {
cache(host);
}
+ if (host) {
+ return (host);
+ }
}
- if (host && host->getNegative()) {
- return (ConstHostPtr());
- }
- return (host);
+ return (ConstHostPtr());
}
.arg(duid ? duid->toText() : "(duid)")
.arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)");
- for (auto it = alternate_sources_.begin();
- !host && // stop at first found
- it != alternate_sources_.end(); ++it) {
+ for (auto source : alternate_sources_) {
if (duid) {
- host = (*it)->get6(subnet_id, duid, HWAddrPtr());
+ host = source->get6(subnet_id, duid, HWAddrPtr());
}
if (!host && hwaddr) {
- host = (*it)->get6(subnet_id, DuidPtr(), hwaddr);
+ host = source->get6(subnet_id, DuidPtr(), hwaddr);
+ }
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
}
- if (host && cache_ptr_ && (it != alternate_sources_.begin())) {
+ if (host && source != cache_ptr_) {
cache(host);
}
+ if (host) {
+ return (host);
+ }
}
- if (host && host->getNegative()) {
- return (ConstHostPtr());
- }
- return (host);
+ return (ConstHostPtr());
}
ConstHostPtr
HOSTS_MGR_ALTERNATE_GET6_PREFIX)
.arg(prefix.toText())
.arg(static_cast<int>(prefix_len));
- for (auto it = alternate_sources_.begin();
- !host && // stop at first found
- it != alternate_sources_.end(); ++it) {
- host = (*it)->get6(prefix, prefix_len);
- if (host && cache_ptr_ && (it != alternate_sources_.begin())) {
+ for (auto source : alternate_sources_) {
+ host = source->get6(prefix, prefix_len);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ }
+ if (host && source != cache_ptr_) {
cache(host);
}
+ if (host) {
+ return (host);
+ }
}
- if (host && host->getNegative()) {
- return (ConstHostPtr());
- }
- return (host);
+ return (ConstHostPtr());
}
ConstHostPtr
.arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
identifier_len));
- for (auto it = alternate_sources_.begin();
- it != alternate_sources_.end(); ++it) {
-
- host = (*it)->get6(subnet_id, identifier_type,
+ for (auto source : alternate_sources_) {
+ host = source->get6(subnet_id, identifier_type,
identifier_begin, identifier_len);
if (host) {
.arg(Host::getIdentifierAsText(identifier_type,
identifier_begin,
identifier_len))
- .arg((*it)->getType())
+ .arg(source->getType())
.arg(host->toText());
- if (cache_ptr_ && (it != alternate_sources_.begin())) {
+ if (source != cache_ptr_) {
cache(host);
}
return (host);
HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6)
.arg(subnet_id)
.arg(addr.toText());
- for (auto it = alternate_sources_.begin();
- !host && // stop at first found
- it != alternate_sources_.end(); ++it) {
- host = (*it)->get6(subnet_id, addr);
- if (host && cache_ptr_ && (it != alternate_sources_.begin())) {
+ for (auto source : alternate_sources_) {
+ host = source->get6(subnet_id, addr);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ }
+ if (host && source != cache_ptr_) {
cache(host);
}
+ if (host) {
+ return (host);
+ }
}
- if (host && host->getNegative()) {
- return (ConstHostPtr());
- }
- return (host);
+ return (ConstHostPtr());
}
void
void
HostMgr::cache(ConstHostPtr host) const {
if (cache_ptr_) {
+ // Need a real host.
+ if (!host || host->getNegative()) {
+ return;
+ }
// Replace any existing value.
int overwrite = 0;
// Don't check the result as it does not matter?
// We can verify other overloads of get4() but the hwaddr/duid is
// not implemented by the memory test backend and the negative cache
- // entry has no IP reservation simply because it was not known when
- // it is created. And there is no by address negative cache because
- // it is not allowed to create a host object, even a fake one, without
- // an identifier. Now for performance the by identifier negative cache
- // is critical, for by address it is better to optimize the out-of-
- // pools case...
+ // entry has no IP reservation when inserted by the host manager.
}
// Check negative cache feature for IPv6.
EXPECT_EQ(0, hcptr_->adds_);
}
+/// @brief Test one backend class.
+///
+/// This class has one entry which is returned to any lookup.
+class TestOneBackend : public BaseHostDataSource {
+public:
+
+ /// Constructor
+ TestOneBackend() : value_() { }
+
+ /// Destructor
+ virtual ~TestOneBackend() { }
+
+ ConstHostCollection getAll(const HWAddrPtr&, const DuidPtr&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAll(const Host::IdentifierType&, const uint8_t*,
+ const size_t) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAll4(const IOAddress&) const {
+ return (getCollection());
+ }
+
+ ConstHostPtr get4(const SubnetID&, const HWAddrPtr&,
+ const DuidPtr&) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get4(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get4(const SubnetID& subnet_id, const IOAddress&) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get6(const SubnetID&, const DuidPtr&,
+ const HWAddrPtr&) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get6(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get6(const IOAddress&, const uint8_t) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get6(const SubnetID& subnet_id, const IOAddress&) const {
+ return (getOne());
+ }
+
+ void add(const HostPtr&) {
+ }
+
+ bool del(const SubnetID&, const IOAddress&) {
+ return (false);
+ }
+
+ bool del4(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) {
+ return (false);
+ }
+
+ bool del6(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) {
+ return (false);
+ }
+
+ std::string getType() const {
+ return ("one");
+ }
+
+ /// Specific methods
+
+ /// @brief Set the entry
+ void setValue(const HostPtr& value) {
+ value_ = value;
+ }
+
+protected:
+ /// @brief Return collection
+ ConstHostCollection getCollection() const {
+ ConstHostCollection hosts;
+ hosts.push_back(value_);
+ return (hosts);
+ }
+
+ /// @brief Return one entry
+ ConstHostPtr getOne() const {
+ return(value_);
+ }
+
+ /// #brief The value
+ HostPtr value_;
+};
+
+/// @brief TestOneBackend pointer type
+typedef boost::shared_ptr<TestOneBackend> TestOneBackendPtr;
+
+/// @brief Test no cache class.
+///
+/// This class looks like a cache but throws when insert() is called.
+class TestNoCache : public MemHostDataSource, public CacheHostDataSource {
+public:
+
+ /// Destructor
+ virtual ~TestNoCache() { }
+
+ /// Override add
+ void add(const HostPtr& host) {
+ isc_throw(NotImplemented,
+ "add is not implemented: " << host->toText());
+ }
+
+ /// Insert throws
+ bool insert(const ConstHostPtr& host, int& overwrite) {
+ isc_throw(NotImplemented,
+ "insert is not implemented: " << host->toText()
+ << " with overwrite: " << overwrite);
+ }
+
+ /// Remove throws
+ bool remove(const HostPtr& host) {
+ isc_throw(NotImplemented,
+ "remove is not implemented: " << host->toText());
+ }
+
+ /// Flush
+ void flush(size_t count) {
+ isc_throw(NotImplemented, "flush is not implemented");
+ }
+
+ /// Size
+ size_t size() const {
+ return (0);
+ }
+
+ /// Capacity
+ size_t capacity() const {
+ return (0);
+ }
+
+ /// Type
+ string getType() const {
+ return ("nocache");
+ }
+};
+
+/// @brief TestNoCache pointer type
+typedef boost::shared_ptr<TestNoCache> TestNoCachePtr;
+
+/// @brief Test fixture for testing generic negative cache handling.
+class NegativeCacheTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ NegativeCacheTest() {
+ HostMgr::create();
+
+ // No cache.
+ ncptr_.reset(new TestNoCache());
+ auto nocacheFactory = [this](const DatabaseConnection::ParameterMap&) {
+ return (ncptr_);
+ };
+ HostDataSourceFactory::registerFactory("nocache", nocacheFactory);
+ HostMgr::addBackend("type=nocache");
+
+ // One backend.
+ obptr_.reset(new TestOneBackend());
+ auto oneFactory = [this](const DatabaseConnection::ParameterMap&) {
+ return (obptr_);
+ };
+ HostDataSourceFactory::registerFactory("one", oneFactory);
+ HostMgr::addBackend("type=one");
+ }
+
+ /// @brief Destructor.
+ virtual ~NegativeCacheTest() {
+ HostDataSourceFactory::deregisterFactory("one");
+ HostDataSourceFactory::deregisterFactory("nocache");
+ }
+
+ /// @brief Test one backend.
+ TestOneBackendPtr obptr_;
+
+ /// @brief Test no cache.
+ TestNoCachePtr ncptr_;
+
+ /// Test methods
+
+ /// @brief Set negative caching.
+ void setNegativeCaching() {
+ ASSERT_TRUE(HostMgr::instance().checkCacheBackend());
+ ASSERT_FALSE(HostMgr::instance().getNegativeCaching());
+ HostMgr::instance().setNegativeCaching(true);
+ ASSERT_TRUE(HostMgr::instance().getNegativeCaching());
+ }
+
+ void testGetAll();
+ void testGet4();
+ void testGet6();
+};
+
+/// Verify that getAll* methods ignore negative caching.
+void NegativeCacheTest::testGetAll() {
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_EQ("192.0.2.1", host->getIPv4Reservation().toText());
+
+ // Make it a negative cached entry.
+ host->setNegative(true);
+ ASSERT_TRUE(host->getNegative());
+
+ // Set the backend with it.
+ obptr_->setValue(host);
+
+ // Verifies getAll* return a collection with it.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts =
+ HostMgr::instance().getAll(host->getHWAddress(), DuidPtr()));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(host, hosts[0]);
+
+ ASSERT_NO_THROW(hosts =
+ HostMgr::instance().getAll(host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(host, hosts[0]);
+
+ ASSERT_NO_THROW(hosts =
+ HostMgr::instance().getAll4(host->getIPv4Reservation()));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(host, hosts[0]);
+}
+
+/// Verify that get4* methods handle negative caching.
+void NegativeCacheTest::testGet4() {
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_EQ("192.0.2.1", host->getIPv4Reservation().toText());
+
+ // Make it a negative cached entry.
+ host->setNegative(true);
+ ASSERT_TRUE(host->getNegative());
+
+ // Set the backend with it.
+ obptr_->setValue(host);
+
+ // Verifies get4 overloads return a null pointer.
+ ConstHostPtr got;
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getHWAddress(), DuidPtr()));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIPv4Reservation()));
+ EXPECT_FALSE(got);
+
+ // Only getAny returns the negative cached entry.
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4Any(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_TRUE(got);
+ EXPECT_EQ(host, got);
+}
+
+/// Verify that get6* methods handle negative caching.
+void NegativeCacheTest::testGet6() {
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID,
+ false);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getDuid());
+
+ // Get the address.
+ IPv6ResrvRange resrvs = host->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(resrvs.first, resrvs.second));
+ const IOAddress& address = resrvs.first->second.getPrefix();
+ ASSERT_EQ("2001:db8::1", address.toText());
+
+ // Make it a negative cached entry.
+ host->setNegative(true);
+ ASSERT_TRUE(host->getNegative());
+
+ // Set the backend with it.
+ obptr_->setValue(host);
+
+ // Verifies get6 overloads return a null pointer.
+ ConstHostPtr got;
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getDuid(),
+ HWAddrPtr()));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got = HostMgr::instance().get6(address, 128));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6(host->getIPv6SubnetID(),
+ address));
+ EXPECT_FALSE(got);
+
+ // Only getAny returns the negative cached entry.
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6Any(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_TRUE(got);
+ EXPECT_EQ(host, got);
+}
+
+/// Verify that getAll* methods ignore negative caching.
+TEST_F(NegativeCacheTest, getAll) {
+ testGetAll();
+}
+
+/// Verify that get4* methods handle negative caching.
+TEST_F(NegativeCacheTest, get4) {
+ testGet4();
+}
+
+/// Verify that get6* methods handle negative caching.
+TEST_F(NegativeCacheTest, get6) {
+ testGet6();
+}
+
+/// Verify that getAll* methods behavior does not change with
+/// host manager negative caching.
+TEST_F(NegativeCacheTest, getAllwithNegativeCaching) {
+ setNegativeCaching();
+ testGetAll();
+}
+
+/// Verify that get4* methods behavior does not change with
+/// host manager negative caching.
+TEST_F(NegativeCacheTest, get4withNegativeCaching) {
+ setNegativeCaching();
+ testGet4();
+}
+
+/// Verify that get6* methods behavior does not change with
+/// host manager negative caching.
+TEST_F(NegativeCacheTest, get6withNegativeCaching) {
+ setNegativeCaching();
+ testGet6();
+}
+
}; // end of anonymous namespace