Browse Source

initial implementation (test + code + doc) of trac #446: config knob for in memory data source

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac446@3950 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
7987955ece

+ 1 - 0
src/bin/auth/Makefile.am

@@ -39,6 +39,7 @@ pkglibexec_PROGRAMS = b10-auth
 b10_auth_SOURCES = auth_srv.cc auth_srv.h
 b10_auth_SOURCES += query.cc query.h
 b10_auth_SOURCES += change_user.cc change_user.h
+b10_auth_SOURCES += config.cc config.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += main.cc
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la

+ 45 - 0
src/bin/auth/auth.spec.pre.in

@@ -7,6 +7,51 @@
         "item_type": "string",
         "item_optional": true,
         "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
+      },
+      { "item_name": "datasources",
+        "item_type": "list",
+        "item_optional": true,
+        "item_default": [],
+	"list_item_spec": {
+          "item_name": "list_element",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+	  "map_item_spec": [
+	    { "item_name": "type",
+	      "item_type": "string",
+	      "item_optional": false,
+	      "item_default": ""
+	    },
+	    { "item_name": "class",
+	      "item_type": "string",
+	      "item_optional": false,
+	      "item_default": "IN"
+	    },
+	    { "item_name": "zones",
+	      "item_type": "list",
+	      "item_optional": false,
+	      "item_default": [],
+	      "list_item_spec": {
+	        "item_name": "list_element",
+	        "item_type": "map",
+	        "item_optional": true,
+	        "map_item_spec": [
+		  { "item_name": "origin",
+		    "item_type": "string",
+		    "item_optional": false,
+		    "item_default": ""
+		  },
+		  { "item_name": "file",
+		    "item_type": "string",
+		    "item_optional": false,
+		    "item_default": ""
+		  }
+		]
+	      }
+	    }
+	  ]
+        }
       }
     ],
     "commands": [

+ 32 - 0
src/bin/auth/auth_srv.cc

@@ -45,6 +45,7 @@
 
 #include <datasrc/query.h>
 #include <datasrc/data_source.h>
+#include <datasrc/memory_datasrc.h>
 #include <datasrc/static_datasrc.h>
 #include <datasrc/sqlite3_datasrc.h>
 
@@ -90,6 +91,9 @@ public:
     bool verbose_mode_;
     AbstractSession* xfrin_session_;
 
+    /// In-memory data source.  Currently class IN only for simplicity.
+    AuthSrv::MemoryDataSrcPtr memory_datasrc_;
+
     /// Hot spot cache
     isc::datasrc::HotCache cache_;
 private:
@@ -290,6 +294,34 @@ AuthSrv::getConfigSession() const {
     return (impl_->config_session_);
 }
 
+AuthSrv::ConstMemoryDataSrcPtr
+AuthSrv::getMemoryDataSrc(const RRClass& rrclass) const {
+    // XXX: for simplicity, we only support the IN class right now.
+    if (rrclass != RRClass::IN()) {
+        isc_throw(InvalidParameter,
+                  "Memory data source is not supported for RR class "
+                  << rrclass);
+    }
+    return (impl_->memory_datasrc_);
+}
+
+void
+AuthSrv::setMemoryDataSrc(const isc::dns::RRClass& rrclass,
+                          MemoryDataSrcPtr memory_datasrc)
+{
+    // XXX: see above
+    if (rrclass != RRClass::IN()) {
+        isc_throw(InvalidParameter,
+                  "Memory data source is not supported for RR class "
+                  << rrclass);
+    }
+    impl_->memory_datasrc_ = memory_datasrc;
+    if (impl_->verbose_mode_) {
+        cerr << "[b10-auth] memory data source is configured for class "
+             << rrclass << endl;
+    }
+}
+
 void
 AuthSrv::processMessage(const IOMessage& io_message, MessagePtr message,
                         OutputBufferPtr buffer, DNSServer* server)

+ 54 - 0
src/bin/auth/auth_srv.h

@@ -19,12 +19,19 @@
 
 #include <string>
 
+// For MemoryDataSrcPtr below.  This should be a temporary definition until
+// we reorganize the data source framework.
+#include <boost/shared_ptr.hpp>
+
 #include <cc/data.h>
 #include <config/ccsession.h>
 
 #include <asiolink/asiolink.h>
 
 namespace isc {
+namespace datasrc {
+class MemoryDataSrc;
+}
 namespace xfr {
 class AbstractXfroutClient;
 }
@@ -224,6 +231,53 @@ public:
     ///
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
 
+    /// A shared pointer type for \c MemoryDataSrc.
+    ///
+    /// This is defined inside the \c AuthSrv class as it's supposed to be
+    /// a short term interface until we integrate the in-memory and other
+    /// data source frameworks.
+    typedef boost::shared_ptr<isc::datasrc::MemoryDataSrc> MemoryDataSrcPtr;
+
+    /// An immutable shared pointer type for \c MemoryDataSrc.
+    typedef boost::shared_ptr<const isc::datasrc::MemoryDataSrc>
+    ConstMemoryDataSrcPtr;
+
+    /// Returns the in-memory data source configured for the \c AuthSrv,
+    /// if any.
+    ///
+    /// The in-memory data source is configured per RR class.  However,
+    /// the data source may not be available for all RR classes.
+    /// If it is not available for the specified RR class, an exception of
+    /// class \c InvalidParameter will be thrown.
+    /// This method never throws an exception otherwise.
+    ///
+    /// Even for supported RR classes, the in-memory data source is not
+    /// configured by default.  In that case a NULL (shared) pointer will
+    /// be returned.
+    ///
+    /// \param rrclass The RR class of the requested in-memory data source.
+    /// \return A pointer to the in-memory data source, if configured;
+    /// otherwise NULL.
+    ConstMemoryDataSrcPtr
+    getMemoryDataSrc(const isc::dns::RRClass& rrclass) const;
+
+    /// Sets or replaces the in-memory data source of the specified RR class.
+    ///
+    /// As noted in \c getMemoryDataSrc(), some RR classes may not be
+    /// supported, in which case an exception of class \c InvalidParameter
+    /// will be thrown.
+    /// This method never throws an exception otherwise.
+    ///
+    /// If there is already an in memory data source configured, it will be
+    /// replaced with the newly specified one.
+    /// \c memory_datasrc can be NULL, in which case it will (re)disable the
+    /// in-memory data source.
+    ///
+    /// \param rrclass The RR class of the in-memory data source to be set.
+    /// \param memory_datasrc A (shared) pointer to \c MemoryDataSrc to be set.
+    void setMemoryDataSrc(const isc::dns::RRClass& rrclass,
+                          MemoryDataSrcPtr memory_datasrc);
+
 private:
     AuthSrvImpl* impl_;
     asiolink::IOService* io_service_;

+ 225 - 0
src/bin/auth/config.cc

@@ -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.
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+#include <datasrc/zonetable.h>
+
+#include <auth/auth_srv.h>
+#include <auth/config.h>
+
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace {
+// Forward declaration
+AuthConfigParser*
+createAuthConfigParser(AuthSrv& server, const std::string& config_id,
+                       bool internal);
+
+/// A derived \c AuthConfigParser class for the "datasources" configuration
+/// identifier.
+class DatasourcesConfig : public AuthConfigParser {
+public:
+    DatasourcesConfig(AuthSrv& server) : server_(server) {}
+    virtual void build(ConstElementPtr config_value);
+    virtual void commit();
+private:
+    AuthSrv& server_;
+    vector<shared_ptr<AuthConfigParser> > datasources_;
+    set<string> configured_sources_;
+};
+
+void
+DatasourcesConfig::build(ConstElementPtr config_value) {
+    BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
+        // The caller is supposed to perform syntax-level checks, but we'll
+        // do minimum level of validation ourselves so that we won't crash due
+        // to a buggy application.
+        ConstElementPtr datasrc_type = datasrc_elem->get("type");
+        if (!datasrc_type) {
+            isc_throw(AuthConfigError, "Missing data source type");
+        }
+
+        if (configured_sources_.find(datasrc_type->stringValue()) !=
+            configured_sources_.end()) {
+            isc_throw(AuthConfigError, "Data source type '" <<
+                      datasrc_type->stringValue() << "' already configured");
+        }
+        
+        shared_ptr<AuthConfigParser> datasrc_config =
+            shared_ptr<AuthConfigParser>(
+                createAuthConfigParser(server_, string("datasources/") +
+                                       datasrc_type->stringValue(),
+                                       true));
+        datasrc_config->build(datasrc_elem);
+        datasources_.push_back(datasrc_config);
+
+        configured_sources_.insert(datasrc_type->stringValue());
+    }
+}
+
+void
+DatasourcesConfig::commit() {
+    BOOST_FOREACH(shared_ptr<AuthConfigParser> datasrc_config, datasources_) {
+        datasrc_config->commit();
+    }
+}
+
+/// A derived \c AuthConfigParser class for the memory type datasource
+/// configuration.  It does not correspond to the configuration syntax;
+/// it's instantiated for internal use.
+class MemoryDatasourceConfig : public AuthConfigParser {
+public:
+    MemoryDatasourceConfig(AuthSrv& server) :
+        server_(server),
+        rrclass_(0)              // XXX: dummy initial value
+    {}
+    virtual void build(ConstElementPtr config_value);
+    virtual void commit();
+private:
+    AuthSrv& server_;
+    RRClass rrclass_;
+    AuthSrv::MemoryDataSrcPtr memory_datasrc_;
+};
+
+void
+MemoryDatasourceConfig::build(ConstElementPtr config_value) {
+    // XXX: apparently we cannot retrieve the default RR class from the
+    // module spec.  As a temporary workaround we hardcode the default value.
+    ConstElementPtr rrclass_elem = config_value->get("class");
+    rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
+
+    // We'd eventually optimize building zones (in case of reloading) by
+    // selectively loading fresh zones.  Right now we simply check the
+    // RR class is supported by the server implementation.
+    server_.getMemoryDataSrc(rrclass_);
+    memory_datasrc_ = AuthSrv::MemoryDataSrcPtr(new MemoryDataSrc());
+
+    ConstElementPtr zones_config = config_value->get("zones");
+    if (!zones_config) {
+        // XXX: Like the RR class, we cannot retrieve the default value here,
+        // so we assume an empty zone list in this case.
+        return;
+    }
+
+    BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
+        ConstElementPtr origin = zone_config->get("origin");
+        if (!origin) {
+            isc_throw(AuthConfigError, "Missing zone origin");
+        }
+        ConstElementPtr file = zone_config->get("file");
+        if (!file) {
+            isc_throw(AuthConfigError, "Missing zone file for zone: "
+                      << origin->str());
+        }
+        const result::Result result = memory_datasrc_->addZone(
+            ZonePtr(new MemoryZone(rrclass_, Name(origin->stringValue()))));
+        if (result == result::EXIST) {
+            isc_throw(AuthConfigError, "zone "<< origin->str()
+                      << " already exists");
+        }
+
+        // TODO
+        // then load the zone from 'file', which is currently not implemented.
+        //
+    }
+}
+
+void
+MemoryDatasourceConfig::commit() {
+    server_.setMemoryDataSrc(rrclass_, memory_datasrc_);
+}
+
+// This is a generalized version of create function that can create
+// a AuthConfigParser object for "internal" use.
+AuthConfigParser*
+createAuthConfigParser(AuthSrv& server, const std::string& config_id,
+                       bool internal)
+{
+    // For the initial implementation we use a naive if-else blocks for
+    // simplicity.  In future we'll probably generalize it using map-like
+    // data structure, and may even provide external register interface so
+    // that it can be dynamically customized.
+    if (config_id == "datasources") {
+        return (new DatasourcesConfig(server));
+    } else if (internal && config_id == "datasources/memory") {
+        return (new MemoryDatasourceConfig(server));
+    } else {
+        isc_throw(AuthConfigError, "Unknown configuration variable: " <<
+                  config_id);
+    }
+}
+} // end of unnamed namespace
+
+AuthConfigParser*
+createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
+    return (createAuthConfigParser(server, config_id, false));
+}
+
+void
+destroyAuthConfigParser(AuthConfigParser* parser) {
+    delete parser;
+}
+
+void
+configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
+    if (!config_set) {
+        isc_throw(AuthConfigError,
+                  "Null pointer is passed to configuration parser");
+    }
+
+    typedef shared_ptr<AuthConfigParser> ParserPtr;
+    vector<ParserPtr> parsers;
+    typedef pair<string, ConstElementPtr> ConfigPair;
+    try {
+        BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+            // We should eventually integrate the sqlite3 DB configuration to
+            // this framework, but to minimize diff we begin with skipping that
+            // part.
+            if (config_pair.first == "database_file") {
+                continue;
+            }
+
+            ParserPtr parser(createAuthConfigParser(server,
+                                                    config_pair.first));
+            parser->build(config_pair.second);
+            parsers.push_back(parser);
+        }
+    } catch (const AuthConfigError& ex) {
+        throw ex;                  // simply rethrowing it
+    } catch (const isc::Exception& ex) {
+        isc_throw(AuthConfigError, "Server configuration failed: " <<
+                  ex.what());
+    }
+
+    BOOST_FOREACH(ParserPtr parser, parsers) {
+        parser->commit();
+    }
+}

+ 196 - 0
src/bin/auth/config.h

@@ -0,0 +1,196 @@
+// 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.
+
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+#ifndef __CONFIG_H
+#define __CONFIG_H 1
+
+class AuthSrv;
+
+/// An exception that is thrown if an error occurs while configuring an
+/// \c AuthSrv object.
+class AuthConfigError : public isc::Exception {
+public:
+    AuthConfigError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// The abstract base class that represents a single configuration identifier
+/// for an \c AuthSrv object.
+///
+/// In general, each top level configuration identifier for \c AuthSrv is
+/// expected to have its own derived class of this base class.
+/// For example, for the following configuration:
+/// \code { "param1": 10, "param2": { "subparam1": "foo", "subparam2": [] } }
+/// \endcode
+/// "param1" and "param2" are top level identifiers, and would correspond to
+/// derived \c AuthConfigParser classes.
+/// "subparam1" and/or "subparam2" may also have dedicated derived classes.
+///
+/// These derived classes are hidden inside the implementation; applications
+/// are not expected to (and in fact cannot) instantiate them directly.
+///
+/// Each derived class is generally expected to be constructed with an
+/// \c AuthSrv object to be configured and hold a reference to the server
+/// throughout the configuration process.
+/// For each derived class, the \c build() method parses the configuration
+/// value for the corresponding identifier and prepares new configuration
+/// value(s) to be applied to the server.  This method may throw an exception
+/// when it encounters an error.
+/// The \c commit() method actually applies the new configuration value
+/// to the server.  It must not throw an exception.
+///
+/// When the destructor is called before \c commit(), the destructor is
+/// supposed to make sure the state of the \c AuthSrv object is the same
+/// as that before it starts building the configuration value.
+/// If \c build() doesn't change the server state (which is recommended)
+/// the destructor doesn't have to do anything special in this regard.
+/// This is a key to ensure the strong exception guarantee (see also
+/// the description of \c configureAuthServer()).
+class AuthConfigParser {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+    AuthConfigParser(const AuthConfigParser& source);
+    AuthConfigParser& operator=(const AuthConfigParser& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    AuthConfigParser() {}
+public:
+    /// The destructor.
+    virtual ~AuthConfigParser() {}
+    //@}
+
+    /// Prepare configuration value.
+    ///
+    /// This method parses the "value part" of the configuration identifier
+    /// that corresponds to this derived class and prepares a new value to
+    /// apply to the server.
+    /// In the above example, the derived class for the identifier "param1"
+    /// would be passed an data \c Element storing an integer whose value
+    /// is 10, and would record that value internally;
+    /// the derived class for the identifier "param2" would be passed a
+    /// map element and (after parsing) convert it into some internal
+    /// data structure.
+    ///
+    /// This method must validate the given value both in terms of syntax
+    /// and semantics of the configuration, so that the server will be
+    /// validly configured at the time of \c commit().  Note: the given
+    /// configuration value is normally syntactically validated, but the
+    /// \c build() implementation must also expect invalid input.  If it
+    /// detects an error it may throw an exception of a derived class
+    /// of \c isc::Exception.
+    ///
+    /// Preparing a configuration value will often require resource
+    /// allocation.  If it fails, it may throw a corresponding standard
+    /// exception.
+    ///
+    /// \param config_value The configuration value for the identifier
+    /// corresponding to the derived class.
+    virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+    /// Apply the prepared configuration value to the server.
+    ///
+    /// This method must be exception free, and, as a consequence, it should
+    /// normally not involve resource allocation.
+    /// Typically it would simply perform exception free assignment or swap
+    /// operation on the value prepared in \c build().
+    ///
+    /// This method is expected to be called after \c build().  The result
+    /// is undefined otherwise.
+    virtual void commit() = 0;
+};
+
+/// Configure an \c AuthSrv object with a set of configuration values.
+///
+/// This function parses configuration information stored in \c config_set
+/// and configures the \c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of \c AuthConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function throws an exception of class \c AuthConfigError.
+/// If the given configuration requires resource allocation and it fails,
+/// a corresponding standard exception will be thrown.
+/// Other exceptions may also be thrown, depending on the implementation of
+/// the underlying derived class of \c AuthConfigError.
+/// In any case the strong guarantee is provided as described above.
+///
+/// \param server The \c AuthSrv object to be configured.
+/// \param config_set A JSON style configuration to apply to \c server.
+void configureAuthServer(AuthSrv& server,
+                         isc::data::ConstElementPtr config_set);
+
+/// Create a new \c AuthConfigParser object for a given configuration
+/// identifier.
+///
+/// It internally identifies an appropriate derived class for the given
+/// identifier and creates a new instance of that class.  The caller can
+/// then configure the \c server regarding the identifier by calling
+/// the \c build() and \c commit() methods of the returned object.
+///
+/// In practice, this function is only expected to be used as a backend of
+/// \c configureAuthServer() and is not supposed to be called directly
+/// by applications.  It is publicly available mainly for testing purposes.
+/// When called directly, the created object must be destroyed using the
+/// \c destroyAuthConfigParser() function.
+///
+/// If the resource allocation for the new object fails, a corresponding
+/// standard exception will be thrown.  Otherwise this function is not
+/// expected to throw an exception, unless the constructor of the underlying
+/// derived class implementation (unexpectedly) throws.
+///
+/// \param server The \c AuthSrv object to be configured.
+/// \param config_id The configuration identifier for which a parser object
+/// is to be created.
+/// \return A pointer to an \c AuthConfigParser object.
+AuthConfigParser* createAuthConfigParser(AuthSrv& server,
+                                         const std::string& config_id);
+
+/// Destroy an \c AuthConfigParser object.
+///
+/// This function destructs the \c parser and frees resources allocated for
+/// it.  \c parser must have been created by \c createAuthConfigParser().
+/// Like the create function, this function is mainly intended to be used
+/// for testing purposes; normal applications are not expected to call it
+/// directly.
+///
+/// This function is not expected to throw an exception unless the underlying
+/// implementation of \c parser (an object of a specific derived class of
+/// \c AuthConfigParser) throws.
+///
+/// \param parser A pointer to an \c AuthConfigParser object to be destroyed.
+void destroyAuthConfigParser(AuthConfigParser* parser);
+
+#endif // __CONFIG_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 7 - 0
src/bin/auth/main.cc

@@ -41,6 +41,7 @@
 
 #include <auth/spec_config.h>
 #include <auth/common.h>
+#include <auth/config.h>
 #include <auth/change_user.h>
 #include <auth/auth_srv.h>
 #include <asiolink/asiolink.h>
@@ -235,7 +236,13 @@ main(int argc, char* argv[]) {
         // from auth_server, and create io_service, auth_server, and
         // sessions in that order.
         auth_server->setXfrinSession(xfrin_session);
+
+        // Configure the server.  configureAuthServer() is expected to install
+        // all initial configurations, but as a short term workaround we
+        // handle the traditional "database_file" setup by directly calling
+        // updateConfig().
         auth_server->setConfigSession(config_session);
+        configureAuthServer(*auth_server, config_session->getFullConfig());
         auth_server->updateConfig(ElementPtr());
 
         cout << "[b10-auth] Server started." << endl;

+ 2 - 0
src/bin/auth/tests/Makefile.am

@@ -21,7 +21,9 @@ run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
+run_unittests_SOURCES += ../config.h ../config.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
+run_unittests_SOURCES += config_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += run_unittests.cc

+ 223 - 0
src/bin/auth/tests/config_unittest.cc

@@ -0,0 +1,223 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+
+#include <xfr/xfrout_client.h>
+
+#include <auth/auth_srv.h>
+#include <auth/config.h>
+
+#include <testutils/mockups.h>
+
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace {
+class AuthConfigTest : public ::testing::Test {
+protected:
+    AuthConfigTest() : rrclass(RRClass::IN()), server(true, xfrout) {}
+    RRClass rrclass;
+    MockXfroutClient xfrout;
+    AuthSrv server;
+};
+
+TEST_F(AuthConfigTest, datasourceConfig) {
+    // By default, we don't have any in-memory data source.
+    EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+    configureAuthServer(server, Element::fromJSON(
+                            "{\"datasources\": [{\"type\": \"memory\"}]}"));
+    // after successful configuration, we should have one (with empty zone).
+    ASSERT_NE(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+    EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(AuthConfigTest, databaseConfig) {
+    // right now, "database_file" is handled separately, so the parser
+    // doesn't recognize it, but it shouldn't throw an exception due to that.
+    EXPECT_NO_THROW(configureAuthServer(
+                        server,
+                        Element::fromJSON(
+                            "{\"database_file\": \"should_be_ignored\"}")));
+}
+
+TEST_F(AuthConfigTest, exceptionGuarantee) {
+    EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+    // This configuration contains an invalid item, which will trigger
+    // an exception.
+    EXPECT_THROW(configureAuthServer(
+                     server,
+                     Element::fromJSON(
+                         "{\"datasources\": [{\"type\": \"memory\"}], "
+                         " \"no_such_config_var\": 1}")),
+                 AuthConfigError);
+    // The server state shouldn't change
+    EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+}
+
+TEST_F(AuthConfigTest, exceptionConversion) {
+    // This configuration contains a bogus RR class, which will trigger an
+    // exception from libdns++.  configureAuthServer() should convert this
+    // to AuthConfigError and rethrow the converted one.
+    EXPECT_THROW(configureAuthServer(
+                     server,
+                     Element::fromJSON(
+                         "{\"datasources\": "
+                         " [{\"type\": \"memory\","
+                         "   \"class\": \"BADCLASS\","
+                         "   \"zones\": [{\"origin\": \"example.com\","
+                         "                \"file\": \"example.zone\"}]}]}")),
+                 AuthConfigError);
+}
+
+TEST_F(AuthConfigTest, badConfig) {
+    // These should normally not happen, but should be handled to avoid
+    // an unexpected crash due to a bug of the caller.
+    EXPECT_THROW(configureAuthServer(server, ElementPtr()), AuthConfigError);
+    EXPECT_THROW(configureAuthServer(server, Element::fromJSON("[]")),
+                                     AuthConfigError);
+}
+
+TEST_F(AuthConfigTest, unknownConfigVar) {
+    EXPECT_THROW(createAuthConfigParser(server, "no_such_config_var"),
+                 AuthConfigError);
+}
+
+class MemoryDatasrcConfigTest : public AuthConfigTest {
+protected:
+    MemoryDatasrcConfigTest() :
+        parser(createAuthConfigParser(server, "datasources"))
+    {}
+    ~MemoryDatasrcConfigTest() {
+        destroyAuthConfigParser(parser);
+    }
+    AuthConfigParser* parser;
+};
+
+TEST_F(MemoryDatasrcConfigTest, addZeroDataSrc) {
+    parser->build(Element::fromJSON("[]"));
+    parser->commit();
+    EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+}
+
+TEST_F(MemoryDatasrcConfigTest, addEmpty) {
+    // By default, we don't have any in-memory data source.
+    EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
+    parser->build(Element::fromJSON("[{\"type\": \"memory\"}]"));
+    parser->commit();
+    EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
+    parser->build(Element::fromJSON("[{\"type\": \"memory\","
+                                    "  \"zones\": []}]"));
+    parser->commit();
+    EXPECT_EQ(0, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, addOneZone) {
+    parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.com\","
+                      "               \"file\": \"example.zone\"}]}]"));
+    parser->commit();
+    EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
+    parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example.com\","
+                      "               \"file\": \"example.zone\"},"
+                      "              {\"origin\": \"example.org\","
+                      "               \"file\": \"example.org.zone\"},"
+                      "              {\"origin\": \"example.net\","
+                      "               \"file\": \"example.net.zone\"}]}]"));
+    parser->commit();
+    EXPECT_EQ(3, server.getMemoryDataSrc(rrclass)->getZoneCount());
+}
+
+TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
+    EXPECT_THROW(parser->build(
+                     Element::fromJSON(
+                         "[{\"type\": \"memory\","
+                         "  \"zones\": [{\"origin\": \"example.com\","
+                         "               \"file\": \"example.zone\"},"
+                         "              {\"origin\": \"example.com\","
+                         "               \"file\": \"example.com.zone\"}]}]")),
+                 AuthConfigError);
+}
+
+TEST_F(MemoryDatasrcConfigTest, addBadZone) {
+    // origin is missing
+    EXPECT_THROW(parser->build(
+                     Element::fromJSON(
+                         "[{\"type\": \"memory\","
+                         "  \"zones\": [{\"file\": \"example.zone\"}]}]")),
+                 AuthConfigError);
+
+    // missing zone file
+    EXPECT_THROW(parser->build(
+                     Element::fromJSON(
+                         "[{\"type\": \"memory\","
+                         "  \"zones\": [{\"origin\": \"example.com\"}]}]")),
+                 AuthConfigError);
+
+    // bogus origin name
+    EXPECT_THROW(parser->build(Element::fromJSON(
+                      "[{\"type\": \"memory\","
+                      "  \"zones\": [{\"origin\": \"example..com\","
+                      "               \"file\": \"example.zone\"}]}]")),
+                 EmptyLabel);
+
+    // bogus RR class name
+    EXPECT_THROW(parser->build(
+                     Element::fromJSON(
+                         "[{\"type\": \"memory\","
+                         "  \"class\": \"BADCLASS\","
+                         "  \"zones\": [{\"origin\": \"example.com\","
+                         "               \"file\": \"example.zone\"}]}]")),
+                 InvalidRRClass);
+
+    // valid RR class, but not currently supported
+    EXPECT_THROW(parser->build(
+                     Element::fromJSON(
+                         "[{\"type\": \"memory\","
+                         "  \"class\": \"CH\","
+                         "  \"zones\": [{\"origin\": \"example.com\","
+                         "               \"file\": \"example.zone\"}]}]")),
+                 isc::InvalidParameter);
+}
+
+TEST_F(MemoryDatasrcConfigTest, badDatasrcType) {
+    EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"badsrc\"}]")),
+                 AuthConfigError);
+    EXPECT_THROW(parser->build(Element::fromJSON("[{\"notype\": \"memory\"}]")),
+                 AuthConfigError);
+    EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": 1}]")),
+                                      isc::data::TypeError);
+    EXPECT_THROW(parser->build(Element::fromJSON("[{\"type\": \"memory\"},"
+                                                 " {\"type\": \"memory\"}]")),
+                 AuthConfigError);
+}
+}

+ 15 - 2
src/lib/datasrc/memory_datasrc.cc

@@ -27,7 +27,10 @@ namespace datasrc {
 /// For now, \c MemoryDataSrc only contains a \c ZoneTable object, which
 /// consists of (pointers to) \c MemoryZone objects, we may add more
 /// member variables later for new features.
-struct MemoryDataSrc::MemoryDataSrcImpl {
+class MemoryDataSrc::MemoryDataSrcImpl {
+public:
+    MemoryDataSrcImpl() : zone_count(0) {}
+    unsigned int zone_count;
     ZoneTable zone_table;
 };
 
@@ -38,13 +41,23 @@ MemoryDataSrc::~MemoryDataSrc() {
     delete impl_;
 }
 
+unsigned int
+MemoryDataSrc::getZoneCount() const {
+    return (impl_->zone_count);
+}
+
 result::Result
 MemoryDataSrc::addZone(ZonePtr zone) {
     if (!zone) {
         isc_throw(InvalidParameter,
                   "Null pointer is passed to MemoryDataSrc::addZone()");
     }
-    return (impl_->zone_table.addZone(zone));
+
+    const result::Result result = impl_->zone_table.addZone(zone);
+    if (result == result::SUCCESS) {
+        ++impl_->zone_count;
+    }
+    return (result);
 }
 
 MemoryDataSrc::FindResult

+ 8 - 1
src/lib/datasrc/memory_datasrc.h

@@ -104,6 +104,13 @@ public:
     ~MemoryDataSrc();
     //@}
 
+    /// Return the number of zones stored in the data source.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The number of zones stored in the data source.
+    unsigned int getZoneCount() const;
+
     /// Add a \c Zone to the \c MemoryDataSrc.
     ///
     /// \c Zone must not be associated with a NULL pointer; otherwise
@@ -140,7 +147,7 @@ public:
     FindResult findZone(const isc::dns::Name& name) const;
 
 private:
-    struct MemoryDataSrcImpl;
+    class MemoryDataSrcImpl;
     MemoryDataSrcImpl* impl_;
 };
 }

+ 20 - 1
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -29,8 +29,9 @@ namespace {
 
 class MemoryDataSrcTest : public ::testing::Test {
 protected:
-    MemoryDataSrcTest()
+    MemoryDataSrcTest() : rrclass(RRClass::IN())
     {}
+    RRClass rrclass;
     MemoryDataSrc memory_datasrc;
 };
 
@@ -112,4 +113,22 @@ TEST_F(MemoryDataSrcTest, add_find_Zone) {
     EXPECT_EQ(Name("i.g.h"),
               memory_datasrc.findZone(Name("z.i.g.h")).zone->getOrigin());
 }
+
+
+TEST_F(MemoryDataSrcTest, getZoneCount) {
+    EXPECT_EQ(0, memory_datasrc.getZoneCount());
+    memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(rrclass, Name("example.com"))));
+    EXPECT_EQ(1, memory_datasrc.getZoneCount());
+
+    // duplicate add.  counter shouldn't change
+    memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(rrclass, Name("example.com"))));
+    EXPECT_EQ(1, memory_datasrc.getZoneCount());
+
+    // add one more
+    memory_datasrc.addZone(
+                  ZonePtr(new MemoryZone(rrclass, Name("example.org"))));
+    EXPECT_EQ(2, memory_datasrc.getZoneCount());
+}
 }