]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3463] Added BindingVariable classes to lease cmds
authorThomas Markwalder <tmark@isc.org>
Thu, 23 Jan 2025 20:11:37 +0000 (15:11 -0500)
committerThomas Markwalder <tmark@isc.org>
Tue, 18 Feb 2025 18:54:19 +0000 (18:54 +0000)
/src/hooks/dhcp/lease_cmds/Makefile.am
    added new files

/src/hooks/dhcp/lease_cmds/binding_variables.cc
/src/hooks/dhcp/lease_cmds/binding_variables.h
     new files

/src/hooks/dhcp/lease_cmds/tests/Makefile.am
    added new file

/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
    new file

src/hooks/dhcp/lease_cmds/Makefile.am
src/hooks/dhcp/lease_cmds/binding_variables.cc [new file with mode: 0644]
src/hooks/dhcp/lease_cmds/binding_variables.h [new file with mode: 0644]
src/hooks/dhcp/lease_cmds/tests/Makefile.am
src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc [new file with mode: 0644]

index c57fe339662056a207326155b5b608cebb6a98ab..65b8f0817df33bb6814d3d2de1ff0940837ade2c 100644 (file)
@@ -20,6 +20,7 @@ liblease_cmds_la_SOURCES += lease_cmds_exceptions.h
 liblease_cmds_la_SOURCES += lease_parser.h lease_parser.cc
 liblease_cmds_la_SOURCES += lease_cmds_log.cc lease_cmds_log.h
 liblease_cmds_la_SOURCES += lease_cmds_messages.cc lease_cmds_messages.h
+liblease_cmds_la_SOURCES += binding_variables.h binding_variables.cc
 liblease_cmds_la_SOURCES += version.cc
 
 liblease_cmds_la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/hooks/dhcp/lease_cmds/binding_variables.cc b/src/hooks/dhcp/lease_cmds/binding_variables.cc
new file mode 100644 (file)
index 0000000..ecbd05b
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Kea Hooks Basic
+// Commercial End User License Agreement v2.0. See COPYING file in the premium/
+// directory.
+
+#include <config.h>
+
+#include <binding_variables.h>
+#include <iostream>
+
+#include <cc/data.h>
+#include <eval/eval_context.h>
+#include <util/multi_threading_mgr.h>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+
+namespace isc {
+namespace lease_cmds {
+
+BindingVariable::BindingVariable(const std::string& name,
+                                 const std::string& expression_str,
+                                 const Source& source,
+                                 uint32_t family)
+    : name_(name), expression_str_(expression_str), source_(source),
+      family_(family) {
+    if (name_.empty()) {
+        isc_throw(BadValue, "BindingVariable - name cannot be empty");
+    }
+
+    /// @todo If we add socpes we may wish to allow higher order
+    /// scopes to override lower scopes with empty expressions.
+    if (expression_str_.empty()) {
+        isc_throw(BadValue, "BindingVariable - '" << name_ 
+                  << "' expression_str cannot be empty");
+    }
+
+    if (family_ != AF_INET && family_ != AF_INET6) {
+        isc_throw(BadValue, "BindingVariable - '" << name_ 
+                  << "', invalid family: " << family_);
+    }
+
+    try {
+        EvalContext eval_ctx(family_ == AF_INET ? Option::V4 : Option::V6);
+        eval_ctx.parseString(expression_str_, EvalContext::PARSER_STRING);
+        expression_.reset(new Expression(eval_ctx.expression_));
+    } catch (const std::exception& ex) {
+        isc_throw(BadValue, "BindingVariable - '" << name_ << "', error parsing expression: '"
+                  << expression_str_ << "' : " << ex.what());
+    }
+}
+
+std::string
+BindingVariable::evaluate(PktPtr packet) const {
+    try {
+        return (evaluateString(*expression_, *packet));
+    } catch (const std::exception& ex) {
+        isc_throw(BadValue, "BindingVariable - " << name_ << ", error evaluating expression: ["
+                  << expression_str_ << "] : " << ex.what());
+    } 
+}
+
+/// @todo Not sure we need CfgElement derivation
+ElementPtr
+BindingVariable::toElement() const {
+    ElementPtr map = Element::createMap();
+    map->set("name", Element::create(name_));
+    map->set("expression_str", Element::create(expression_str_));
+    map->set("source", Element::create((source_ == QUERY ? "query" : "response")));
+    // family_ is contextual
+    return (map);
+}
+
+BindingVariableCache::BindingVariableCache() 
+    : variables_(), mutex_(new std::mutex) {
+}
+
+void
+BindingVariableCache::cacheVariable(BindingVariablePtr variable) {
+    util::MultiThreadingLock lock(*mutex_);
+    variables_.push_back(variable);
+}
+
+void 
+BindingVariableCache::clear() {
+    util::MultiThreadingLock lock(*mutex_);
+    // Discard contents.
+    // We use modification time to remember the last time we flushed.
+    variables_.clear();
+    updateModificationTime();
+}
+
+size_t
+BindingVariableCache::size() {
+    util::MultiThreadingLock lock(*mutex_);
+    return (variables_.size());
+}
+
+boost::posix_time::ptime
+BindingVariableCache::getLastFlushTime() {
+    util::MultiThreadingLock lock(*mutex_);
+    return (BaseStampedElement::getModificationTime());
+}
+
+/// @brief Tag for the name index.
+//struct VariableNameTag { };
+
+/// @brief Tag for the source index.
+//struct VariableSourceTag { };
+
+
+BindingVariableListPtr
+BindingVariableCache::getAll() {
+    util::MultiThreadingLock lock(*mutex_);
+
+    BindingVariableListPtr var_list(new BindingVariableList());
+    const auto& index = variables_.get<VariableSequenceTag>();
+    for (auto const& variable : index) {
+        /// For now we'll return the pointer, w/o making a copy
+        /// of the  varaiable itself.  We never updates variables
+        /// so we should be OK.
+        var_list->push_back(variable);
+    }
+
+    return (var_list);
+}
+
+BindingVariablePtr
+BindingVariableCache::getByName(const std::string& name) {
+    util::MultiThreadingLock lock(*mutex_);
+
+    const auto& index = variables_.get<VariableNameTag>();
+    auto var_iter = index.find(name);
+    return (var_iter == index.end() ? BindingVariablePtr() : *var_iter);
+}
+
+BindingVariableListPtr
+BindingVariableCache::getBySource(const BindingVariable::Source& source) {
+    util::MultiThreadingLock lock(*mutex_);
+
+    BindingVariableListPtr var_list(new BindingVariableList());
+    const auto& index = variables_.get<VariableSourceTag>();
+    auto lower_limit = index.lower_bound(source);
+    auto upper_limit = index.upper_bound(source);
+    for (auto var_iter = lower_limit; var_iter != upper_limit; ++var_iter) {
+        var_list->push_back(*var_iter);
+    }
+
+    return (var_list);
+}
+    
+} // end of namespace lease_cmds
+} // end of namespace isc
diff --git a/src/hooks/dhcp/lease_cmds/binding_variables.h b/src/hooks/dhcp/lease_cmds/binding_variables.h
new file mode 100644 (file)
index 0000000..6c7c88c
--- /dev/null
@@ -0,0 +1,231 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Kea Hooks Basic
+// Commercial End User License Agreement v2.0. See COPYING file in the premium/
+// directory.
+
+#ifndef BINDING_VARIABLES_H
+#define BINDING_VARIABLES_H
+
+#include <cc/base_stamped_element.h>
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <eval/evaluate.h>
+#include <eval/token.h>
+#include <dhcp/pkt.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Embodies a named expression, whose output when
+/// evaluated can be stored in a lease's user-context.
+class BindingVariable : public isc::data::CfgToElement {
+public:
+    /// @brief Specifies the packet that the expression should be
+    /// evaluated against.
+    enum Source {
+        QUERY,
+        RESPONSE
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param name name of the variable, must be unique. Used
+    /// both as the key and as the label for the value in the output.
+    /// @param expression_str Evaluation expression text.
+    /// @param source Source packet the expression should be
+    /// evaluated against, either QUERY or RESPONSE.
+    /// @param family Protocol family of the expression, either
+    /// AF_INET or AF_INET6.
+    ///
+    /// During construction the expression string is parsed for the
+    /// protocol family.
+    /// @throw BadValue if name if empty, or expression string fails
+    /// to parse.
+    explicit BindingVariable(const std::string& name,
+                             const std::string& expression_str,
+                             const Source& source,
+                             uint32_t family);
+
+    /// @brief Destructor
+    virtual ~BindingVariable() = default;
+
+    /// @brief Evaluate the variable against the given packet.
+    ///
+    /// @param packet Pointer to the target packet.
+    /// @return string result of the evaluation.
+    /// @throw BadValue if an evaluation error occurs.
+    std::string evaluate(dhcp::PktPtr packet) const;
+
+    /// @brief Fetches the variable's name.
+    ///
+    /// @return string containing the name.
+    std::string getName() const {
+        return (name_);
+    }
+
+    /// @brief Fetches the variable's pre-parsed expression string.
+    ///
+    /// @return string containing the expression.
+    std::string getExpressionStr() const {
+        return (expression_str_);
+    }
+
+    /// @brief Fetches the variable's parsed expression.
+    ///
+    /// @return pointer to the expression.
+    dhcp::ExpressionPtr getExpression() const {
+        return (expression_);
+    }
+
+    /// @brief Fetches the variable's packet source.
+    ///
+    /// @return Source of the packet.
+    Source getSource() const {
+        return (source_);
+    }
+
+    /// @brief Fetches the variable's protocol family. 
+    ///
+    /// @return Family of the packet i.e AF_INET or AF_INET6.
+    uint32_t getFamily() const {
+        return (family_);
+    }
+
+    /// @todo Not sure we need CfgElement derivation
+    virtual data::ElementPtr toElement() const;
+
+private:
+    /// @param source Source packet the expression should be
+    /// evaluated against, either QUERY or RESPONSE.
+    /// @param family Protocol family of the expression, either
+
+    /// @brief name of the variable.
+    std::string name_;
+
+    /// @brief Evaluation expression text.
+    std::string expression_str_;
+
+    /// @brief Source packet the expression should be evaluated against.
+    Source source_;
+
+    /// @brief Protocol family AF_INET or AF_INET6.
+    uint32_t family_;
+
+    /// @brief Parsed evaluation expression.
+    dhcp::ExpressionPtr expression_;
+};
+
+/// @brief Defines a shared pointer to a BindingVariable.
+typedef boost::shared_ptr<BindingVariable> BindingVariablePtr;
+
+/// @brief Defines a list of BindingVariablePtr instances.
+typedef std::list<BindingVariablePtr> BindingVariableList;
+
+/// @brief Defines a pointer to a list of BindingVariablePtrs.
+typedef boost::shared_ptr<BindingVariableList> BindingVariableListPtr;
+
+/// @brief Tag for the sequence index.
+struct VariableSequenceTag { };
+
+/// @brief Tag for the name index.
+struct VariableNameTag { };
+
+/// @brief Tag for the source index.
+struct VariableSourceTag { };
+
+/// @brief the client class multi-index.
+typedef boost::multi_index_container<
+    BindingVariablePtr,
+    boost::multi_index::indexed_by<
+        // First index is by sequence. -- Do we need this one?
+        boost::multi_index::sequenced<
+            boost::multi_index::tag<VariableSequenceTag>
+        >,
+        // Second index is by name.
+        boost::multi_index::hashed_unique<
+            boost::multi_index::tag<VariableNameTag>,
+            boost::multi_index::const_mem_fun<BindingVariable,
+                                              std::string,
+                                              &BindingVariable::getName>
+        >,
+
+        // Third index is by source.
+        boost::multi_index::ordered_non_unique<
+            boost::multi_index::tag<VariableSourceTag>,
+            boost::multi_index::const_mem_fun<BindingVariable,
+                                              BindingVariable::Source,
+                                              &BindingVariable::getSource>
+        >
+    >
+> BindingVariableContainer;
+
+/// @brief BindingVariableCache stores binding variables.
+///
+/// Wrapper around the variable container that provides
+/// thread-safe access and time-stamped management.  The
+/// later is available if/when supported scopes beyond
+/// global are added.
+class BindingVariableCache : public data::BaseStampedElement {
+public:
+    /// @brief Constructor
+    BindingVariableCache();
+
+    /// @brief Destructor
+    virtual ~BindingVariableCache() = default;
+
+    /// @brief Adds (or replaces) the variable in the cache.
+    ///
+    /// @param variable pointer to the variable to store.
+    void cacheVariable(BindingVariablePtr variable);
+
+    /// @brief Delete all the entries in the cache.
+    void clear();
+
+    /// @brief Returns number of entries in the cache.
+    size_t size();
+
+    /// @brief Returns the last time the cache was flushed (or
+    /// the time it was created if it has never been flushed).
+    boost::posix_time::ptime getLastFlushTime();
+
+    /// @brief Fetches all of the binding variables in the order
+    /// they were added to the cache.
+    ///
+    /// @return Pointer to a list of the BindingVariables.
+    BindingVariableListPtr getAll();
+
+    /// @brief Fetches a binding variable by name
+    ///
+    /// @return A pointer to the variable or an empty pointer
+    /// if no match is found.
+    BindingVariablePtr getByName(const std::string& name);
+
+    /// @brief Fetches all of the binding variables in the order
+    /// they were added to the cache that use a specific source.
+    ///
+    /// @return Pointer to a list of the BindingVariables.
+    BindingVariableListPtr getBySource(const BindingVariable::Source& source);
+
+private:
+    /// @brief Variable storage container.
+    BindingVariableContainer variables_;
+
+    /// @brief The mutex used to protect internal state.
+    const boost::scoped_ptr<std::mutex> mutex_;
+
+};
+
+/// @brief Defines a shared pointer to a BindingVariableCache.
+typedef boost::shared_ptr<BindingVariableCache> BindingVariableCachePtr;
+
+} // end of namespace lease_cmds
+} // end of namespace isc
+#endif
index cb73aa25df243be2883f85e2a3bd5af1682cc994..5c947eefc2e9e2c6ff7f1b59f846f330ed4a1355 100644 (file)
@@ -30,6 +30,7 @@ lease_cmds_unittests_SOURCES = run_unittests.cc
 lease_cmds_unittests_SOURCES += lease_cmds_unittest.h lease_cmds_unittest.cc
 lease_cmds_unittests_SOURCES += lease_cmds4_unittest.cc
 lease_cmds_unittests_SOURCES += lease_cmds6_unittest.cc
+lease_cmds_unittests_SOURCES += binding_variables_unittest.cc
 
 lease_cmds_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
@@ -37,7 +38,8 @@ lease_cmds_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
 
 lease_cmds_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 
-lease_cmds_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+lease_cmds_unittests_LDADD  = $(top_builddir)/src/hooks/dhcp/lease_cmds/liblease_cmds.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
 lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
 lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
 lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
diff --git a/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc b/src/hooks/dhcp/lease_cmds/tests/binding_variables_unittest.cc
new file mode 100644 (file)
index 0000000..8bd8bee
--- /dev/null
@@ -0,0 +1,234 @@
+// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+
+#include <binding_variables.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/user_context_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::test;
+
+using namespace isc::lease_cmds;
+
+namespace {
+
+/// @brief Test BindingVariable valid construction scenarios.
+TEST(BindingVariableTest, validConstructor) {
+    BindingVariablePtr bv;
+
+    struct Scenario {
+        uint32_t line_;
+        std::string name_;
+        std::string expression_str_;
+        BindingVariable::Source source_;
+        uint32_t family_;
+    };
+
+    std::string valid_v4_exp = "pkt4.mac";
+    std::string valid_v6_exp = "pkt6.transid";
+
+    std::list<Scenario> scenarios = {
+        {
+           __LINE__,  "my-var", valid_v4_exp, BindingVariable::QUERY ,AF_INET
+        },
+        {
+           __LINE__,  "my-var", valid_v4_exp, BindingVariable::RESPONSE ,AF_INET
+        },
+        {
+           __LINE__,  "my-var", valid_v6_exp, BindingVariable::QUERY, AF_INET6
+        },
+        {
+           __LINE__,  "my-var", valid_v6_exp, BindingVariable::RESPONSE, AF_INET6
+        }
+    };
+
+    for (auto const& scenario : scenarios) {
+        ASSERT_NO_THROW_LOG(bv.reset(new BindingVariable(scenario.name_,
+                                                         scenario.expression_str_,
+                                                         scenario.source_,
+                                                         scenario.family_)));
+        ASSERT_TRUE(bv);
+        EXPECT_EQ(bv->getName(), scenario.name_);
+        EXPECT_EQ(bv->getExpressionStr(), scenario.expression_str_);
+        ASSERT_TRUE(bv->getExpression());
+        EXPECT_EQ(bv->getSource(), scenario.source_);
+        EXPECT_EQ(bv->getFamily(), scenario.family_);
+    }
+}
+
+/// @brief Test BindingVariable invalid construction scenarios.
+TEST(BindingVariableTest, invalidConstructor) {
+    BindingVariablePtr bv;
+
+    struct Scenario {
+        uint32_t line_;
+        std::string name_;
+        std::string expression_str_;
+        uint32_t family_;
+        std::string expected_error_;
+    };
+
+    std::string valid_v4_exp = "pkt4.mac";
+    std::string valid_v6_exp = "pkt6.transid";
+
+    std::list<Scenario> scenarios = {
+        {
+           __LINE__,  "", valid_v4_exp, AF_INET,
+           "BindingVariable - name cannot be empty"
+        },
+        {
+           __LINE__,  "my-var", "", AF_INET,
+           "BindingVariable - 'my-var' expression_str cannot be empty"
+        },
+        {
+           __LINE__,  "my-var", "bogus + stuff", AF_INET,
+           "BindingVariable - 'my-var', error parsing expression: "
+           "'bogus + stuff' : <string>:1.1: Invalid character: b"
+        },
+        {
+           __LINE__,  "my-var", valid_v4_exp, 99,
+           "BindingVariable - 'my-var', invalid family: 99"
+        },
+        {
+           __LINE__,  "my-var", valid_v4_exp, AF_INET6,
+           "BindingVariable - 'my-var', error parsing expression: "
+           "'pkt4.mac' : <string>:1.1-4: pkt4 can only be used in DHCPv4."
+        },
+        {
+           __LINE__,  "my-var", valid_v6_exp, AF_INET,
+           "BindingVariable - 'my-var', error parsing expression: "
+           "'pkt6.transid' : <string>:1.1-4: pkt6 can only be used in DHCPv6."
+        }
+    };
+
+    for (auto const& scenario : scenarios) {
+        ASSERT_THROW_MSG(bv.reset(new BindingVariable(scenario.name_,
+                                                      scenario.expression_str_,
+                                                      BindingVariable::QUERY,
+                                                      scenario.family_)),
+                        BadValue, scenario.expected_error_);
+    }
+}
+
+TEST(BindingVariableCacheTest, basics) {
+
+    auto ref_time = boost::posix_time::second_clock::local_time();
+
+    // Create a new cache.
+    BindingVariableCachePtr cache(new BindingVariableCache());
+
+    // Verify last flush time has been set to approximately now.
+    EXPECT_GE(cache->getLastFlushTime(), ref_time);
+    ref_time = cache->getLastFlushTime();
+
+    // Ensure getters return empty lists or pointers without harm.
+    BindingVariableListPtr var_list;
+    ASSERT_NO_THROW_LOG(var_list = cache->getAll());
+    ASSERT_TRUE(var_list);
+    EXPECT_EQ(var_list->size(), 0);
+
+    BindingVariablePtr var;
+    ASSERT_NO_THROW_LOG(var = cache->getByName("foo"));
+    ASSERT_FALSE(var);
+
+    ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::QUERY));
+    ASSERT_TRUE(var_list);
+    EXPECT_EQ(var_list->size(), 0);
+
+    ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::RESPONSE));
+    ASSERT_TRUE(var_list);
+    EXPECT_EQ(var_list->size(), 0);
+
+    // Add four variables.
+    std::string valid_v6_exp = "pkt6.transid";
+    BindingVariableList ref_list;
+    ref_list.push_back(BindingVariablePtr(new BindingVariable("one", valid_v6_exp,
+                                                              BindingVariable::QUERY,
+                                                              AF_INET6)));
+
+    ref_list.push_back(BindingVariablePtr(new BindingVariable("two", valid_v6_exp,
+                                                              BindingVariable::RESPONSE,
+                                                              AF_INET6)));
+
+    ref_list.push_back(BindingVariablePtr(new BindingVariable("three", valid_v6_exp,
+                                                              BindingVariable::RESPONSE,
+                                                              AF_INET6)));
+
+    ref_list.push_back(BindingVariablePtr(new BindingVariable("four", valid_v6_exp,
+                                                              BindingVariable::QUERY,
+                                                              AF_INET6)));
+
+    for (auto const& ref_iter : ref_list) {
+        ASSERT_NO_THROW_LOG(cache->cacheVariable(ref_iter));
+    }
+
+    // Make sure getAll() returns all four in order added.
+    ASSERT_NO_THROW_LOG(var_list = cache->getAll());
+    ASSERT_TRUE(var_list);
+    EXPECT_EQ(var_list->size(), 4);
+
+    auto var_iter = var_list->begin();
+    for (auto const& ref_iter : ref_list) {
+        EXPECT_EQ((*var_iter)->getName(), ref_iter->getName());
+        EXPECT_EQ((*var_iter)->getSource(), ref_iter->getSource());
+        ++var_iter;
+    }
+
+    // Make sure getByName() can return each one.
+    for (auto const& ref_iter : ref_list) {
+        ASSERT_NO_THROW_LOG(var = cache->getByName(ref_iter->getName()));
+        ASSERT_TRUE(var);
+        EXPECT_EQ(var->getName(), ref_iter->getName());
+    }
+
+    // Make sure getBySource() works for QUERY.
+    ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::QUERY));
+    ASSERT_TRUE(var_list);
+    ASSERT_EQ(var_list->size(), 2);
+
+    var_iter = var_list->begin();
+    for (auto const& ref_iter : ref_list) {
+        if (ref_iter->getSource() == BindingVariable::QUERY) {
+            EXPECT_EQ((*var_iter)->getName(), ref_iter->getName());
+            ++var_iter;
+        }
+    }
+
+    // Make sure getBySource() works for RESPONSE.
+    ASSERT_NO_THROW_LOG(var_list = cache->getBySource(BindingVariable::RESPONSE));
+    ASSERT_TRUE(var_list);
+    ASSERT_EQ(var_list->size(), 2);
+
+    var_iter = var_list->begin();
+    for (auto const& ref_iter : ref_list) {
+        if (ref_iter->getSource() == BindingVariable::RESPONSE) {
+            EXPECT_EQ((*var_iter)->getName(), ref_iter->getName());
+            ++var_iter;
+        }
+    }
+
+    // Make sure last flush time hasn't been touched.
+    EXPECT_EQ(cache->getLastFlushTime(), ref_time);
+
+    // Sleep 1s so we can check flush time gets updated.
+    usleep(1000000);
+    ASSERT_NO_THROW_LOG(cache->clear());
+    EXPECT_EQ(cache->size(), 0);
+
+    EXPECT_GT(cache->getLastFlushTime(), ref_time);
+}
+
+} // end of anonymous namespace