Browse Source

[master] Merge branch 'trac3569_rebase' (host data source configuration)

Tomek Mrugalski 9 years ago
parent
commit
8896c9c3f7

+ 74 - 5
doc/guide/dhcp4-srv.xml

@@ -425,17 +425,17 @@ be followed by a comma and another object definition.</para>
 </section>
 </section>
 
 
 <section id="database-configuration4">
 <section id="database-configuration4">
-  <title>Database Configuration</title>
+  <title>Lease Database Configuration</title>
 
 
   <note>
   <note>
-    <para>Database access information must be configured for the DHCPv4 server,
-    even if it has already been configured for the DHCPv6 server.  The servers
+    <para>Lease database access information must be configured for the DHCPv4 server,
+    even if it has already been configured for the DHCPv6 server. The servers
     store their information independently, so each server can use a separate
     store their information independently, so each server can use a separate
     database or both servers can use the same database.</para>
     database or both servers can use the same database.</para>
   </note>
   </note>
 
 
-  <para>Database configuration is controlled through the Dhcp4/lease-database
-  parameters. The type of the database must be set to "mysql" or "postgresql",
+  <para>Lease database configuration is controlled through the Dhcp4/lease-database
+  parameters. The type of the database must be set to "memfile", "mysql" or "postgresql",
   e.g.
   e.g.
 <screen>
 <screen>
 "Dhcp4": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
 "Dhcp4": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
@@ -471,6 +471,75 @@ be followed by a comma and another object definition.</para>
 </section>
 </section>
 </section>
 </section>
 
 
+<section>
+  <title>Hosts Storage</title>
+
+    <note>
+      <para>This feature did not undergo the regular system level
+      testing conducted by ISC. As such, please treat it as
+      experimental.</para>
+    </note>
+
+    <para>Kea is also able to store information about host reservations in the
+    database. Hosts database configuration uses the same syntax as lease
+    database. In fact, Kea server opens independent connections for each
+    purpose, be it lease or hosts information. This gives the solution most
+    flexibility. Kea can be used to keep leases and host reservations
+    separately, but can also point to the same database. Currently the only
+    supported hosts database type is MySQL.</para>
+
+    <para>Please note that usage of hosts storage is optional. User can define
+    all host reservations in the configuration file. That is the recommended way
+    if the number of reservations is small. However, with the number of
+    reservations growing it's more convenient to use host storage. Please note
+    that both storages (configuration file and MySQL) can be used together. If
+    hosts are defined in both places, the definitions from configuration file
+    are checked first and external storage is checked later, if
+    necessary.</para>
+
+    <para>All hosts leases issued by the server are stored in the hosts
+    database. Currently there is only one available backend: MySQL. Other host
+    backends will become available in future Kea versions.</para>
+
+<section id="hosts-database-configuration4">
+  <title>IPv4 Hosts Database Configuration</title>
+
+  <para>Hosts database configuration is controlled through the Dhcp4/hosts-database
+  parameters. If enabled, the type of the database must be set to "mysql". Other
+  hosts backends may be added in later Kea versions.
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
+</screen>
+  Next, the name of the database to hold the leases must be set: this is the
+  name used when the lease database was created (see <xref linkend="mysql-database-create"/>).
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"name": "<replaceable>database-name</replaceable>" </userinput>, ... }, ... }
+</screen>
+  If the database is located on a different system to the DHCPv4 server, the
+  database host name must also be specified (although it should be noted that this
+  configuration may have a severe impact on server performance):
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"host": <replaceable>remote-host-name</replaceable></userinput>, ... }, ... }
+</screen>
+  The usual state of affairs will be to have the database on the same machine as
+  the DHCPv4 server.  In this case, set the value to the empty string:
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"host" : ""</userinput>, ... }, ... }
+</screen>
+  </para>
+  <para>Finally, the credentials of the account under which the server will
+  access the database should be set:
+<screen>
+"Dhcp4": { "hosts-database": { <userinput>"user": "<replaceable>user-name</replaceable>"</userinput>,
+                               <userinput>"password": "<replaceable>password</replaceable>"</userinput>,
+                              ... },
+           ... }
+</screen>
+  If there is no password to the account, set the password to the empty string
+  "". (This is also the default.)</para>
+</section>
+</section>
+
 <section id="dhcp4-interface-configuration">
 <section id="dhcp4-interface-configuration">
   <title>Interface configuration</title>
   <title>Interface configuration</title>
   <para>The DHCPv4 server has to be configured to listen on specific network
   <para>The DHCPv4 server has to be configured to listen on specific network

+ 82 - 5
doc/guide/dhcp6-srv.xml

@@ -424,18 +424,18 @@ be followed by a comma and another object definition.</para>
 </section>
 </section>
 
 
 <section id="database-configuration6">
 <section id="database-configuration6">
-  <title>Database Configuration</title>
+  <title>Lease Database Configuration</title>
 
 
   <note>
   <note>
-    <para>Database access information must be configured for the DHCPv6 server,
+    <para>Lease database access information must be configured for the DHCPv6 server,
     even if it has already been configured for the DHCPv4 server.  The servers
     even if it has already been configured for the DHCPv4 server.  The servers
     store their information independently, so each server can use a separate
     store their information independently, so each server can use a separate
     database or both servers can use the same database.</para>
     database or both servers can use the same database.</para>
   </note>
   </note>
 
 
-  <para>Database configuration is controlled through the Dhcp6/lease-database
-  parameters. The type of the database must be set to "mysql" or "postgresql",
-  e.g.
+  <para>Lease database configuration is controlled through the
+  Dhcp6/lease-database parameters. The type of the database must be set to
+  "memfile", "mysql" or "postgresql", e.g.
 <screen>
 <screen>
 "Dhcp6": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
 "Dhcp6": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
 </screen>
 </screen>
@@ -470,6 +470,83 @@ be followed by a comma and another object definition.</para>
 </section>
 </section>
 </section>
 </section>
 
 
+<!-- Uncomment this once 4212 is merged. While the host storage for
+     MySQL is there, it is able to store hosts, but not IPv6
+     reservations. So technically we have host reservations for v6 in
+     MySQL, but in practice it's kinda useless right now as it can't
+     store addresses or prefixes. Let's keep it commented out until
+     we implement that functionality
+
+<section>
+  <title>Hosts Storage</title>
+
+    <note>
+      <para>This feature did not undergo the regular system level
+      testing conducted by ISC. As such, please treat it as
+      experimental.</para>
+    </note>
+
+    <para>Kea is also able to store information about host reservations in the
+    database. Hosts database configuration uses the same syntax as lease
+    database. In fact, Kea server opens independent connections for each
+    purpose, be it lease or hosts information. This gives the solution most
+    flexibility. Kea can be used to keep leases and host reservations
+    separately, but can also point to the same database. Currently the only
+    supported hosts database type is MySQL.</para>
+
+    <para>Please note that usage of hosts storage is optional. User can define
+    all host reservations in the configuration file. That is the recommended way
+    if the number of reservations is small. However, with the number of
+    reservations growing it's more convenient to use host storage. Please note
+    that both storages (configuration file and MySQL) can be used together. If
+    hosts are defined in both places, the definitions from configuration file
+    are checked first and external storage is checked later, if
+    necessary.</para>
+
+    <para>All hosts leases issued by the server are stored in the hosts
+    database. Currently there is only one available backend: MySQL. Other host
+    backends will become available in future Kea versions.</para>
+
+<section id="hosts-database-configuration6">
+  <title>IPv6 Hosts Database Configuration</title>
+
+  <para>Hosts database configuration is controlled through the Dhcp6/hosts-database
+  parameters. If enabled, the type of the database must be set to "mysql". Other
+  hosts backends may be added in later Kea versions.
+<screen>
+"Dhcp6": { "hosts-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
+</screen>
+  Next, the name of the database to hold the leases must be set: this is the
+  name used when the lease database was created (see <xref linkend="mysql-database-create"/>).
+<screen>
+"Dhcp6": { "hosts-database": { <userinput>"name": "<replaceable>database-name</replaceable>" </userinput>, ... }, ... }
+</screen>
+  If the database is located on a different system to the DHCPv6 server, the
+  database host name must also be specified (although it should be noted that this
+  configuration may have a severe impact on server performance):
+<screen>
+"Dhcp6": { "hosts-database": { <userinput>"host": <replaceable>remote-host-name</replaceable></userinput>, ... }, ... }
+</screen>
+  The usual state of affairs will be to have the database on the same machine as
+  the DHCPv6 server.  In this case, set the value to the empty string:
+<screen>
+"Dhcp6": { "hosts-database": { <userinput>"host" : ""</userinput>, ... }, ... }
+</screen>
+  </para>
+  <para>Finally, the credentials of the account under which the server will
+  access the database should be set:
+<screen>
+"Dhcp6": { "hosts-database": { <userinput>"user": "<replaceable>user-name</replaceable>"</userinput>,
+                               <userinput>"password": "<replaceable>password</replaceable>"</userinput>,
+                              ... },
+           ... }
+</screen>
+  If there is no password to the account, set the password to the empty string
+  "". (This is also the default.)</para>
+</section>
+</section>
+-->
+
 <section id="dhcp6-interface-selection">
 <section id="dhcp6-interface-selection">
   <title>Interface selection</title>
   <title>Interface selection</title>
   <para>The DHCPv6 server has to be configured to listen on specific network
   <para>The DHCPv6 server has to be configured to listen on specific network

+ 5 - 1
src/bin/dhcp4/json_config_parser.cc

@@ -438,7 +438,11 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
         parser  = new StringParser(config_id,
         parser  = new StringParser(config_id,
                                     globalContext()->string_values_);
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
     } else if (config_id.compare("lease-database") == 0) {
-        parser = new DbAccessParser(config_id, *globalContext());
+        parser = new DbAccessParser(config_id, DbAccessParser::LEASE_DB,
+                                    *globalContext());
+    } else if (config_id.compare("hosts-database") == 0) {
+        parser = new DbAccessParser(config_id, DbAccessParser::HOSTS_DB,
+                                    *globalContext());
     } else if (config_id.compare("hooks-libraries") == 0) {
     } else if (config_id.compare("hooks-libraries") == 0) {
         parser = new HooksLibrariesParser(config_id);
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("echo-client-id") == 0) {
     } else if (config_id.compare("echo-client-id") == 0) {

+ 29 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -39,6 +39,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/utils.h>
+#include <dhcpsrv/host_mgr.h>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <stats/stats_mgr.h>
 #include <stats/stats_mgr.h>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -74,7 +75,31 @@ const char* CONFIGS[] = {
         "    \"valid-lifetime\": 4000,"
         "    \"valid-lifetime\": 4000,"
         "    \"interface\": \"eth0\" "
         "    \"interface\": \"eth0\" "
         " } ],"
         " } ],"
-        "\"valid-lifetime\": 4000 }"
+    "\"valid-lifetime\": 4000 }",
+
+    // Configuration 1:
+    // - 1 subnet: 192.0.2.0/24
+    // - MySQL Host Data Source configured
+    "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"hosts-database\": {"
+        "    \"type\": \"mysql\","
+        "    \"name\": \"keatest\","
+        "    \"user\": \"keatest\","
+        "    \"password\": \"keatest\""
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface\": \"eth0\" "
+        " } ],"
+    "\"valid-lifetime\": 4000 }"
 };
 };
 
 
 // This test verifies that the destination address of the response
 // This test verifies that the destination address of the response
@@ -2634,4 +2659,7 @@ TEST_F(Dhcpv4SrvTest, statisticsUnknownRcvd) {
     EXPECT_EQ(1, drop_stat->getInteger().first);
     EXPECT_EQ(1, drop_stat->getInteger().first);
 }
 }
 
 
+/// @todo: Implement proper tests for MySQL lease/host database,
+///        see ticket #4214.
+
 }; // end of anonymous namespace
 }; // end of anonymous namespace

+ 5 - 1
src/bin/dhcp6/json_config_parser.cc

@@ -689,7 +689,11 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
         parser  = new StringParser(config_id,
         parser  = new StringParser(config_id,
                                    globalContext()->string_values_);
                                    globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
     } else if (config_id.compare("lease-database") == 0) {
-        parser = new DbAccessParser(config_id, *globalContext());
+        parser = new DbAccessParser(config_id, DbAccessParser::LEASE_DB,
+                                    *globalContext());
+    } else if (config_id.compare("hosts-database") == 0) {
+        parser = new DbAccessParser(config_id, DbAccessParser::HOSTS_DB,
+                                    *globalContext());
     } else if (config_id.compare("hooks-libraries") == 0) {
     } else if (config_id.compare("hooks-libraries") == 0) {
         parser = new HooksLibrariesParser(config_id);
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("dhcp-ddns") == 0) {
     } else if (config_id.compare("dhcp-ddns") == 0) {

+ 51 - 23
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -34,6 +34,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/utils.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 #include <util/range_utilities.h>
 #include <util/range_utilities.h>
@@ -61,6 +62,53 @@ using namespace std;
 
 
 namespace {
 namespace {
 
 
+const char* CONFIGS[] = {
+    // Configuration 0:
+    // - used in advertiseOptions
+    "{ \"interfaces-config\": {"
+    "  \"interfaces\": [ \"*\" ]"
+    "},"
+    "\"preferred-lifetime\": 3000,"
+    "\"rebind-timer\": 2000, "
+    "\"renew-timer\": 1000, "
+    "\"subnet6\": [ { "
+    "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+    "    \"subnet\": \"2001:db8:1::/48\", "
+    "    \"interface\": \"eth0\", "
+    "    \"option-data\": [ {"
+    "          \"name\": \"dns-servers\","
+    "          \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\""
+    "        },"
+    "        {"
+    "          \"name\": \"subscriber-id\","
+    "          \"data\": \"1234\","
+    "          \"csv-format\": False"
+    "        } ]"
+    " } ],"
+    "\"valid-lifetime\": 4000 }",
+
+    // Configuration 1:
+    // - a single subnet
+    // - MySQL host data source
+    "{ \"interfaces-config\": {"
+    "  \"interfaces\": [ \"*\" ]"
+    "},"
+    "\"hosts-database\": {"
+    "    \"type\": \"mysql\","
+    "    \"name\": \"keatest\","
+    "    \"user\": \"keatest\","
+    "    \"password\": \"keatest\""
+    "},"
+    "\"preferred-lifetime\": 3000,"
+    "\"rebind-timer\": 2000, "
+    "\"renew-timer\": 1000, "
+    "\"subnet6\": [ { "
+    "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+    "    \"subnet\": \"2001:db8:1::/48\" "
+    " } ],"
+    "\"valid-lifetime\": 4000 }"
+};
+
 // This test verifies that incoming SOLICIT can be handled properly when
 // This test verifies that incoming SOLICIT can be handled properly when
 // there are no subnets configured.
 // there are no subnets configured.
 //
 //
@@ -288,28 +336,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     IfaceMgrTestConfig test_config(true);
     IfaceMgrTestConfig test_config(true);
 
 
     ConstElementPtr x;
     ConstElementPtr x;
-    string config = "{ \"interfaces-config\": {"
-        "  \"interfaces\": [ \"*\" ]"
-        "},"
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
-        "\"subnet6\": [ { "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
-        "    \"subnet\": \"2001:db8:1::/48\", "
-        "    \"interface\": \"eth0\", "
-        "    \"option-data\": [ {"
-        "          \"name\": \"dns-servers\","
-        "          \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\""
-        "        },"
-        "        {"
-        "          \"name\": \"subscriber-id\","
-        "          \"data\": \"1234\","
-        "          \"csv-format\": False"
-        "        } ]"
-        " } ],"
-        "\"valid-lifetime\": 4000 }";
-    ASSERT_NO_THROW(configure(config));
+    ASSERT_NO_THROW(configure(CONFIGS[0]));
 
 
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -2707,9 +2734,10 @@ TEST_F(Dhcpv6SrvTest, receiveParseFailedStat) {
     EXPECT_EQ(1, recv_drop->getInteger().first);
     EXPECT_EQ(1, recv_drop->getInteger().first);
 }
 }
 
 
-
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 /// to call processX() methods.
 
 
+/// @todo: Implement proper tests for MySQL lease/host database,
+///        see ticket #4214.
 
 
 }   // end of anonymous namespace
 }   // end of anonymous namespace

+ 4 - 0
src/lib/dhcpsrv/base_host_data_source.h

@@ -20,6 +20,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/host.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
@@ -214,6 +215,9 @@ public:
     virtual void rollback() {};
     virtual void rollback() {};
 };
 };
 
 
+/// @brief HostDataSource pointer
+typedef boost::shared_ptr<BaseHostDataSource> HostDataSourcePtr;
+
 }
 }
 }
 }
 
 

+ 4 - 2
src/lib/dhcpsrv/host_data_source_factory.cc

@@ -39,9 +39,9 @@ namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
 
 
-boost::scoped_ptr<BaseHostDataSource>&
+HostDataSourcePtr&
 HostDataSourceFactory::getHostDataSourcePtr() {
 HostDataSourceFactory::getHostDataSourcePtr() {
-    static boost::scoped_ptr<BaseHostDataSource> hostDataSourcePtr;
+    static HostDataSourcePtr hostDataSourcePtr;
     return (hostDataSourcePtr);
     return (hostDataSourcePtr);
 }
 }
 
 
@@ -99,6 +99,7 @@ HostDataSourceFactory::destroy() {
     getHostDataSourcePtr().reset();
     getHostDataSourcePtr().reset();
 }
 }
 
 
+#if 0
 BaseHostDataSource&
 BaseHostDataSource&
 HostDataSourceFactory::instance() {
 HostDataSourceFactory::instance() {
     BaseHostDataSource* hdsptr = getHostDataSourcePtr().get();
     BaseHostDataSource* hdsptr = getHostDataSourcePtr().get();
@@ -108,6 +109,7 @@ HostDataSourceFactory::instance() {
     }
     }
     return (*hdsptr);
     return (*hdsptr);
 }
 }
+#endif
 
 
 }; // namespace dhcp
 }; // namespace dhcp
 }; // namespace isc
 }; // namespace isc

+ 1 - 12
src/lib/dhcpsrv/host_data_source_factory.h

@@ -88,23 +88,12 @@ public:
     /// host data source is available.
     /// host data source is available.
     static void destroy();
     static void destroy();
 
 
-    /// @brief Return current host data source
-    ///
-    /// @returns An instance of the "current" host data source.  An exception
-    /// will be thrown if none is available.
-    ///
-    /// @throw NoHostDataSourceManager No host data source is available: use
-    ///        create() to create one before calling this method.
-    static BaseHostDataSource& instance();
-
-private:
     /// @brief Hold pointer to host data source instance
     /// @brief Hold pointer to host data source instance
     ///
     ///
     /// Holds a pointer to the singleton host data source.  The singleton
     /// Holds a pointer to the singleton host data source.  The singleton
     /// is encapsulated in this method to avoid a "static initialization
     /// is encapsulated in this method to avoid a "static initialization
     /// fiasco" if defined in an external static variable.
     /// fiasco" if defined in an external static variable.
-    static boost::scoped_ptr<BaseHostDataSource>& getHostDataSourcePtr();
-
+    static HostDataSourcePtr& getHostDataSourcePtr();
 };
 };
 
 
 
 

+ 26 - 19
src/lib/dhcpsrv/host_mgr.cc

@@ -38,8 +38,6 @@ namespace dhcp {
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 
 
-boost::shared_ptr<BaseHostDataSource> HostMgr::alternate_source;
-
 boost::scoped_ptr<HostMgr>&
 boost::scoped_ptr<HostMgr>&
 HostMgr::getHostMgrPtr() {
 HostMgr::getHostMgrPtr() {
     static boost::scoped_ptr<HostMgr> host_mgr_ptr;
     static boost::scoped_ptr<HostMgr> host_mgr_ptr;
@@ -51,11 +49,20 @@ HostMgr::create(const std::string& access) {
     getHostMgrPtr().reset(new HostMgr());
     getHostMgrPtr().reset(new HostMgr());
 
 
     if (!access.empty()) {
     if (!access.empty()) {
+        // If the user specified parameters, let's pass them to the create
+        // method. It will destroy any prior instances and will create
+        // the new one.
         HostDataSourceFactory::create(access);
         HostDataSourceFactory::create(access);
-
-        /// @todo Initialize alternate_source here.
-        //alternate_source = HostDataSourceFactory::getHostDataSourcePtr();
+    } else {
+        // Ok, no parameters were specified. We should destroy the existing
+        // insteance.
+        HostDataSourceFactory::destroy();
     }
     }
+
+    // Now store the host data source pointer. It may be NULL. That's ok as
+    // NULL value indicates that there's no host data source configured.
+    getHostMgrPtr()->alternate_source_ =
+        HostDataSourceFactory::getHostDataSourcePtr();
 }
 }
 
 
 HostMgr&
 HostMgr&
@@ -70,8 +77,8 @@ HostMgr::instance() {
 ConstHostCollection
 ConstHostCollection
 HostMgr::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
 HostMgr::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
     ConstHostCollection hosts = getCfgHosts()->getAll(hwaddr, duid);
     ConstHostCollection hosts = getCfgHosts()->getAll(hwaddr, duid);
-    if (alternate_source) {
-        ConstHostCollection hosts_plus = alternate_source->getAll(hwaddr, duid);
+    if (alternate_source_) {
+        ConstHostCollection hosts_plus = alternate_source_->getAll(hwaddr, duid);
         hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
         hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
     }
     }
     return (hosts);
     return (hosts);
@@ -80,8 +87,8 @@ HostMgr::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
 ConstHostCollection
 ConstHostCollection
 HostMgr::getAll4(const IOAddress& address) const {
 HostMgr::getAll4(const IOAddress& address) const {
     ConstHostCollection hosts = getCfgHosts()->getAll4(address);
     ConstHostCollection hosts = getCfgHosts()->getAll4(address);
-    if (alternate_source) {
-        ConstHostCollection hosts_plus = alternate_source->getAll4(address);
+    if (alternate_source_) {
+        ConstHostCollection hosts_plus = alternate_source_->getAll4(address);
         hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
         hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
     }
     }
     return (hosts);
     return (hosts);
@@ -91,13 +98,13 @@ ConstHostPtr
 HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
 HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
               const DuidPtr& duid) const {
               const DuidPtr& duid) const {
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, hwaddr, duid);
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, hwaddr, duid);
-    if (!host && alternate_source) {
+    if (!host && alternate_source_) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
                   HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_HWADDR_DUID)
                   HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_HWADDR_DUID)
             .arg(subnet_id)
             .arg(subnet_id)
             .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
             .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)")
             .arg(duid ? duid->toText() : "(duid)");
             .arg(duid ? duid->toText() : "(duid)");
-        host = alternate_source->get4(subnet_id, hwaddr, duid);
+        host = alternate_source_->get4(subnet_id, hwaddr, duid);
     }
     }
     return (host);
     return (host);
 }
 }
@@ -106,12 +113,12 @@ ConstHostPtr
 HostMgr::get4(const SubnetID& subnet_id,
 HostMgr::get4(const SubnetID& subnet_id,
               const asiolink::IOAddress& address) const {
               const asiolink::IOAddress& address) const {
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, address);
     ConstHostPtr host = getCfgHosts()->get4(subnet_id, address);
-    if (!host && alternate_source) {
+    if (!host && alternate_source_) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
                   HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4)
                   HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4)
             .arg(subnet_id)
             .arg(subnet_id)
             .arg(address.toText());
             .arg(address.toText());
-        host = alternate_source->get4(subnet_id, address);
+        host = alternate_source_->get4(subnet_id, address);
     }
     }
     return (host);
     return (host);
 }
 }
@@ -121,13 +128,13 @@ ConstHostPtr
 HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
 HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
                const HWAddrPtr& hwaddr) const {
                const HWAddrPtr& hwaddr) const {
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, duid, hwaddr);
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, duid, hwaddr);
-    if (!host && alternate_source) {
+    if (!host && alternate_source_) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
                   HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_DUID_HWADDR)
                   HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_DUID_HWADDR)
             .arg(subnet_id)
             .arg(subnet_id)
             .arg(duid ? duid->toText() : "(duid)")
             .arg(duid ? duid->toText() : "(duid)")
             .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)");
             .arg(hwaddr ? hwaddr->toText() : "(no-hwaddr)");
-        host = alternate_source->get6(subnet_id, duid, hwaddr);
+        host = alternate_source_->get6(subnet_id, duid, hwaddr);
     }
     }
     return (host);
     return (host);
 }
 }
@@ -135,12 +142,12 @@ HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
 ConstHostPtr
 ConstHostPtr
 HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
 HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
     ConstHostPtr host = getCfgHosts()->get6(prefix, prefix_len);
     ConstHostPtr host = getCfgHosts()->get6(prefix, prefix_len);
-    if (!host && alternate_source) {
+    if (!host && alternate_source_) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
                   HOSTS_MGR_ALTERNATE_GET6_PREFIX)
                   HOSTS_MGR_ALTERNATE_GET6_PREFIX)
             .arg(prefix.toText())
             .arg(prefix.toText())
             .arg(static_cast<int>(prefix_len));
             .arg(static_cast<int>(prefix_len));
-        host = alternate_source->get6(prefix, prefix_len);
+        host = alternate_source_->get6(prefix, prefix_len);
     }
     }
     return (host);
     return (host);
 }
 }
@@ -149,12 +156,12 @@ ConstHostPtr
 HostMgr::get6(const SubnetID& subnet_id,
 HostMgr::get6(const SubnetID& subnet_id,
               const asiolink::IOAddress& addr) const {
               const asiolink::IOAddress& addr) const {
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, addr);
     ConstHostPtr host = getCfgHosts()->get6(subnet_id, addr);
-    if (!host && alternate_source) {
+    if (!host && alternate_source_) {
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
         LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
                   HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6)
                   HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6)
             .arg(subnet_id)
             .arg(subnet_id)
             .arg(addr.toText());
             .arg(addr.toText());
-        host = alternate_source->get6(subnet_id, addr);
+        host = alternate_source_->get6(subnet_id, addr);
     }
     }
     return (host);
     return (host);
 }
 }

+ 9 - 1
src/lib/dhcpsrv/host_mgr.h

@@ -207,6 +207,14 @@ public:
         return (std::string("host_mgr"));
         return (std::string("host_mgr"));
     }
     }
 
 
+    /// @brief Returns pointer to the host data source
+    ///
+    /// May return NULL
+    /// @return pointer to the host data source (or NULL)
+    HostDataSourcePtr getHostDataSource() const {
+        return (alternate_source_);
+    }
+
 private:
 private:
 
 
     /// @brief Private default constructor.
     /// @brief Private default constructor.
@@ -215,7 +223,7 @@ private:
     /// @brief Pointer to an alternate host data source.
     /// @brief Pointer to an alternate host data source.
     ///
     ///
     /// If this pointer is NULL, the source is not in use.
     /// If this pointer is NULL, the source is not in use.
-    static boost::shared_ptr<BaseHostDataSource> alternate_source;
+    HostDataSourcePtr alternate_source_;
 
 
     /// @brief Returns a pointer to the currently used instance of the
     /// @brief Returns a pointer to the currently used instance of the
     /// @c HostMgr.
     /// @c HostMgr.

+ 1 - 0
src/lib/dhcpsrv/mysql_connection.h

@@ -19,6 +19,7 @@
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <mysql.h>
 #include <mysql.h>
 #include <vector>
 #include <vector>
+#include <stdint.h>
 
 
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {

+ 27 - 6
src/lib/dhcpsrv/parsers/dbaccess_parser.cc

@@ -17,6 +17,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
@@ -34,8 +35,9 @@ namespace dhcp {
 
 
 
 
 // Factory function to build the parser
 // Factory function to build the parser
-DbAccessParser::DbAccessParser(const std::string&, const ParserContext& ctx)
-    : values_(), ctx_(ctx)
+DbAccessParser::DbAccessParser(const std::string&, DBType db_type,
+                               const ParserContext& ctx)
+    : values_(), type_(db_type), ctx_(ctx)
 {
 {
 }
 }
 
 
@@ -139,11 +141,30 @@ DbAccessParser::getDbAccessString() const {
 // Commit the changes - reopen the database with the new parameters
 // Commit the changes - reopen the database with the new parameters
 void
 void
 DbAccessParser::commit() {
 DbAccessParser::commit() {
-    // Close current lease manager database.
-    LeaseMgrFactory::destroy();
 
 
-    // ... and open the new database using the access string.
-    LeaseMgrFactory::create(getDbAccessString());
+    switch (type_) {
+    case LEASE_DB:
+    {
+        // Close current lease manager database.
+        LeaseMgrFactory::destroy();
+
+        // ... and open the new database using the access string.
+        LeaseMgrFactory::create(getDbAccessString());
+        break;
+    }
+    case HOSTS_DB:
+    {
+        // Let's instantiate HostMgr with new parameters. Note that HostMgr's
+        // create method will call HostDataSourceFactory::create() with
+        // appropriate parameters. It will also destroy a pre-existing
+        // instance, if it existed.
+        HostMgr::create(getDbAccessString());
+        break;
+    }
+    default:
+        isc_throw(BadValue, "Incorrect type specified in DbAccessParser: "
+                  << type_);
+    };
 }
 }
 
 
 };  // namespace dhcp
 };  // namespace dhcp

+ 20 - 2
src/lib/dhcpsrv/parsers/dbaccess_parser.h

@@ -45,6 +45,13 @@ public:
 /// depend on the database chosen.
 /// depend on the database chosen.
 class DbAccessParser: public DhcpConfigParser {
 class DbAccessParser: public DhcpConfigParser {
 public:
 public:
+
+    /// @brief Specifies the database type
+    typedef enum {
+        LEASE_DB = 1,
+        HOSTS_DB = 2
+    } DBType;
+
     /// @brief Keyword and associated value
     /// @brief Keyword and associated value
     typedef std::pair<std::string, std::string> StringPair;
     typedef std::pair<std::string, std::string> StringPair;
 
 
@@ -55,8 +62,10 @@ public:
     ///
     ///
     /// @param param_name Name of the parameter under which the database
     /// @param param_name Name of the parameter under which the database
     ///        access details are held.
     ///        access details are held.
+    /// @param db_type Specifies database type (lease or hosts)
     /// @param ctx Parser context.
     /// @param ctx Parser context.
-    DbAccessParser(const std::string& param_name, const ParserContext& ctx);
+    DbAccessParser(const std::string& param_name, DBType db_type,
+                   const ParserContext& ctx);
 
 
     /// The destructor.
     /// The destructor.
     virtual ~DbAccessParser()
     virtual ~DbAccessParser()
@@ -103,7 +112,14 @@ public:
     ///         destroying the parser after use.
     ///         destroying the parser after use.
     static DhcpConfigParser* factory(const std::string& param_name,
     static DhcpConfigParser* factory(const std::string& param_name,
                                      const ParserContext& ctx) {
                                      const ParserContext& ctx) {
-        return (new DbAccessParser(param_name, ctx));
+        if (param_name == "lease-database") {
+            return (new DbAccessParser(param_name, DbAccessParser::LEASE_DB, ctx));
+        } else if (param_name == "hosts-database") {
+            return (new DbAccessParser(param_name, DbAccessParser::HOSTS_DB, ctx));
+        } else {
+            isc_throw(BadValue, "Unexpected parameter name (" << param_name
+                      << ") passed to DbAccessParser::factory");
+        }
     }
     }
 
 
 protected:
 protected:
@@ -130,6 +146,8 @@ private:
 
 
     std::map<std::string, std::string> values_; ///< Stored parameter values
     std::map<std::string, std::string> values_; ///< Stored parameter values
 
 
+    DBType type_; ///< Database type (leases or hosts)
+
     ParserContext ctx_; ///< Parser context
     ParserContext ctx_; ///< Parser context
 };
 };
 
 

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -104,6 +104,7 @@ libdhcpsrv_unittests_SOURCES += generic_host_data_source_unittest.cc generic_hos
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 if HAVE_MYSQL
 if HAVE_MYSQL
+libdhcpsrv_unittests_SOURCES += mysql_schema.cc mysql_schema.h
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
 libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
 endif
 endif

+ 88 - 18
src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc

@@ -17,6 +17,8 @@
 #include <cc/command_interpreter.h>
 #include <cc/command_interpreter.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
+#include <dhcpsrv/tests/mysql_schema.h>
+#include <dhcpsrv/host_mgr.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -27,6 +29,7 @@
 using namespace std;
 using namespace std;
 using namespace isc;
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::data;
 using namespace isc::data;
 using namespace isc::config;
 using namespace isc::config;
 
 
@@ -201,8 +204,9 @@ public:
     /// @brief Constructor
     /// @brief Constructor
     ///
     ///
     /// @brief Keyword/value collection of ddatabase access parameters
     /// @brief Keyword/value collection of ddatabase access parameters
-    TestDbAccessParser(const std::string& param_name, const ParserContext& ctx)
-        : DbAccessParser(param_name, ctx)
+    TestDbAccessParser(const std::string& param_name, DbAccessParser::DBType type,
+                       const ParserContext& ctx)
+        : DbAccessParser(param_name, type, ctx)
     {}
     {}
 
 
     /// @brief Destructor
     /// @brief Destructor
@@ -243,7 +247,8 @@ TEST_F(DbAccessParserTest, validTypeMemfile) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
     checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
 }
 }
@@ -259,7 +264,8 @@ TEST_F(DbAccessParserTest, emptyKeyword) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
     checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
 }
 }
@@ -276,7 +282,8 @@ TEST_F(DbAccessParserTest, persistV4Memfile) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
 
 
     checkAccessString("Valid memfile", parser.getDbAccessParameters(),
     checkAccessString("Valid memfile", parser.getDbAccessParameters(),
@@ -295,7 +302,8 @@ TEST_F(DbAccessParserTest, persistV6Memfile) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V6));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V6));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
 
 
     checkAccessString("Valid memfile", parser.getDbAccessParameters(),
     checkAccessString("Valid memfile", parser.getDbAccessParameters(),
@@ -314,7 +322,8 @@ TEST_F(DbAccessParserTest, validLFCInterval) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V6));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V6));
     ASSERT_NO_THROW(parser.build(json_elements));
     ASSERT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid LFC Interval", parser.getDbAccessParameters(),
     checkAccessString("Valid LFC Interval", parser.getDbAccessParameters(),
                       config, Option::V6);
                       config, Option::V6);
@@ -332,7 +341,8 @@ TEST_F(DbAccessParserTest, negativeLFCInterval) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V6));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V6));
     EXPECT_THROW(parser.build(json_elements), BadValue);
     EXPECT_THROW(parser.build(json_elements), BadValue);
 }
 }
 
 
@@ -348,7 +358,8 @@ TEST_F(DbAccessParserTest, largeLFCInterval) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V6));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V6));
     EXPECT_THROW(parser.build(json_elements), BadValue);
     EXPECT_THROW(parser.build(json_elements), BadValue);
 }
 }
 
 
@@ -365,7 +376,8 @@ TEST_F(DbAccessParserTest, validTypeMysql) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
     checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
     checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
 }
 }
@@ -382,7 +394,8 @@ TEST_F(DbAccessParserTest, missingTypeKeyword) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
     EXPECT_THROW(parser.build(json_elements), TypeKeywordMissing);
 }
 }
 
 
@@ -442,7 +455,8 @@ TEST_F(DbAccessParserTest, incrementalChanges) {
                              "name",     "keatest",
                              "name",     "keatest",
                              NULL};
                              NULL};
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
 
 
     // First configuration string should cause a representation of that string
     // First configuration string should cause a representation of that string
     // to be held.
     // to be held.
@@ -506,7 +520,8 @@ TEST_F(DbAccessParserTest, getDbAccessString) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
 
 
     // Get the database access string
     // Get the database access string
@@ -518,10 +533,10 @@ TEST_F(DbAccessParserTest, getDbAccessString) {
     EXPECT_EQ(dbaccess, "name=keatest type=mysql universe=4");
     EXPECT_EQ(dbaccess, "name=keatest type=mysql universe=4");
 }
 }
 
 
-// Check that the "commit" function actually opens the database.  We will
-// only do this for the "memfile" database, as that does not assume that the
-// test has been built with MySQL support.
-TEST_F(DbAccessParserTest, commit) {
+// Check that the "commit" function actually opens the database, when type
+// is set to LEASE_DB.  We will only do this for the "memfile" database, as
+// that does not assume that the test has been built with MySQL support.
+TEST_F(DbAccessParserTest, commitLeaseDb) {
 
 
     // Verify that no lease database is open
     // Verify that no lease database is open
     EXPECT_THROW({
     EXPECT_THROW({
@@ -536,7 +551,8 @@ TEST_F(DbAccessParserTest, commit) {
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     ConstElementPtr json_elements = Element::fromJSON(json_config);
     EXPECT_TRUE(json_elements);
     EXPECT_TRUE(json_elements);
 
 
-    TestDbAccessParser parser("lease-database", ParserContext(Option::V4));
+    TestDbAccessParser parser("lease-database", DbAccessParser::LEASE_DB,
+                              ParserContext(Option::V4));
     EXPECT_NO_THROW(parser.build(json_elements));
     EXPECT_NO_THROW(parser.build(json_elements));
 
 
     // Ensure that the access string is as expected.
     // Ensure that the access string is as expected.
@@ -552,4 +568,58 @@ TEST_F(DbAccessParserTest, commit) {
     EXPECT_EQ(std::string("memfile"), dbtype);
     EXPECT_EQ(std::string("memfile"), dbtype);
 }
 }
 
 
+#ifdef HAVE_MYSQL
+// Check that the "commit" function actually opens the database, when type
+// is set to HOSTS_DB. We're using MySQL here. Since the only currently supported
+// host data source is the one that uses MySQL, we have no other choice, but to
+// depend this test on MYSQL availability.
+TEST_F(DbAccessParserTest, commitHostsDb) {
+
+    // Remove schema (if there are any leftovers from previous tests).
+    destroyMySQLSchema();
+
+    // Verify that no lease database is open
+    EXPECT_THROW({
+            LeaseMgr& manager = LeaseMgrFactory::instance();
+            manager.getType();  // Never executed but satisfies compiler
+            }, isc::dhcp::NoLeaseManager);
+
+    // Set up the parser to open the memfile database.
+    const char* config[] = {"type", "mysql", "user", "keatest",
+                            "password", "keatest", "name", "keatest", NULL};
+    string json_config = toJson(config);
+
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser("hosts-database", DbAccessParser::HOSTS_DB,
+                              ParserContext(Option::V4));
+    EXPECT_NO_THROW(parser.build(json_elements));
+
+    // Ensure that the access string is as expected.
+    EXPECT_EQ("name=keatest password=keatest type=mysql universe=4 user=keatest",
+              parser.getDbAccessString());
+
+    // Destroy lease mgr (if there's any)
+    LeaseMgrFactory::destroy();
+
+    EXPECT_NO_THROW(createMySQLSchema());
+
+    // Committal of the parser changes should not create LeaseMgr.
+    // It should create HostDataSource instead.
+    EXPECT_NO_THROW(parser.commit());
+
+    // Check that LeaseMgr was NOT created (it shouldn't, this is for HOSTS_DB).
+    EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager);
+
+    // Verify that HostDataSource has been created.
+    HostDataSourcePtr hds = HostMgr::instance().getHostDataSource();
+    ASSERT_TRUE(hds);
+
+    EXPECT_EQ("mysql", hds->getType());
+
+    EXPECT_NO_THROW(destroyMySQLSchema());
+}
+#endif
+
 };  // Anonymous namespace
 };  // Anonymous namespace

+ 1 - 1
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc

@@ -27,7 +27,7 @@ namespace dhcp {
 namespace test {
 namespace test {
 
 
 GenericHostDataSourceTest::GenericHostDataSourceTest()
 GenericHostDataSourceTest::GenericHostDataSourceTest()
-    :hdsptr_(NULL) {
+    :hdsptr_() {
 
 
 }
 }
 
 

+ 1 - 1
src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h

@@ -125,7 +125,7 @@ public:
                               const ClientClasses& classes2);
                               const ClientClasses& classes2);
 
 
     /// @brief Pointer to the host data source
     /// @brief Pointer to the host data source
-    BaseHostDataSource* hdsptr_;
+    HostDataSourcePtr hdsptr_;
 
 
     /// @brief Test that checks that simple host with IPv4 reservation
     /// @brief Test that checks that simple host with IPv4 reservation
     ///        can be inserted and later retrieved.
     ///        can be inserted and later retrieved.

+ 14 - 118
src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc

@@ -20,6 +20,7 @@
 #include <dhcpsrv/mysql_connection.h>
 #include <dhcpsrv/mysql_connection.h>
 #include <dhcpsrv/mysql_host_data_source.h>
 #include <dhcpsrv/mysql_host_data_source.h>
 #include <dhcpsrv/tests/generic_host_data_source_unittest.h>
 #include <dhcpsrv/tests/generic_host_data_source_unittest.h>
+#include <dhcpsrv/tests/mysql_schema.h>
 #include <dhcpsrv/host_data_source_factory.h>
 #include <dhcpsrv/host_data_source_factory.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -38,111 +39,6 @@ using namespace std;
 
 
 namespace {
 namespace {
 
 
-// This holds statements to create and destroy the schema.
-#include "schema_mysql_copy.h"
-
-// Connection strings.
-// Database: keatest
-// Host: localhost
-// Username: keatest
-// Password: keatest
-const char* VALID_TYPE = "type=mysql";
-const char* INVALID_TYPE = "type=unknown";
-const char* VALID_NAME = "name=keatest";
-const char* INVALID_NAME = "name=invalidname";
-const char* VALID_HOST = "host=localhost";
-const char* INVALID_HOST = "host=invalidhost";
-const char* VALID_USER = "user=keatest";
-const char* INVALID_USER = "user=invaliduser";
-const char* VALID_PASSWORD = "password=keatest";
-const char* INVALID_PASSWORD = "password=invalid";
-
-// Given a combination of strings above, produce a connection string.
-string connectionString(const char* type, const char* name, const char* host,
-                        const char* user, const char* password) {
-    const string space = " ";
-    string result = "";
-
-    if (type != NULL) {
-        result += string(type);
-    }
-    if (name != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(name);
-    }
-
-    if (host != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(host);
-    }
-
-    if (user != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(user);
-    }
-
-    if (password != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(password);
-    }
-
-    return (result);
-}
-
-// Return valid connection string
-string
-validConnectionString() {
-    return (connectionString(VALID_TYPE, VALID_NAME, VALID_HOST,
-                             VALID_USER, VALID_PASSWORD));
-}
-
-// @brief Clear everything from the database
-//
-// There is no error checking in this code: if something fails, one of the
-// tests will (should) fall over.
-void destroySchema() {
-    MySqlHolder mysql;
-
-    // Open database
-    (void) mysql_real_connect(mysql, "localhost", "keatest",
-                              "keatest", "keatest", 0, NULL, 0);
-
-    // Get rid of everything in it.
-    for (int i = 0; destroy_statement[i] != NULL; ++i) {
-        (void) mysql_query(mysql, destroy_statement[i]);
-    }
-}
-
-// @brief Create the Schema
-//
-// Creates all the tables in what is assumed to be an empty database.
-//
-// There is no error checking in this code: if it fails, one of the tests
-// will fall over.
-void createSchema() {
-    MySqlHolder mysql;
-
-    // Open database
-    (void) mysql_real_connect(mysql, "localhost", "keatest",
-                              "keatest", "keatest", 0, NULL, 0);
-
-    // Execute creation statements.
-    for (int i = 0; create_statement[i] != NULL; ++i) {
-        ASSERT_EQ(0, mysql_query(mysql, create_statement[i]))
-            << "Failed on statement " << i << ": " << create_statement[i];
-    }
-}
-
-
-
 class MySqlHostDataSourceTest : public GenericHostDataSourceTest {
 class MySqlHostDataSourceTest : public GenericHostDataSourceTest {
 public:
 public:
     /// @brief Constructor
     /// @brief Constructor
@@ -151,12 +47,12 @@ public:
     MySqlHostDataSourceTest() {
     MySqlHostDataSourceTest() {
 
 
         // Ensure schema is the correct one.
         // Ensure schema is the correct one.
-        destroySchema();
-        createSchema();
+        destroyMySQLSchema();
+        createMySQLSchema();
 
 
         // Connect to the database
         // Connect to the database
         try {
         try {
-            HostDataSourceFactory::create(validConnectionString());
+            HostDataSourceFactory::create(validMySQLConnectionString());
         } catch (...) {
         } catch (...) {
             std::cerr << "*** ERROR: unable to open database. The test\n"
             std::cerr << "*** ERROR: unable to open database. The test\n"
                          "*** environment is broken and must be fixed before\n"
                          "*** environment is broken and must be fixed before\n"
@@ -166,7 +62,7 @@ public:
             throw;
             throw;
         }
         }
 
 
-        hdsptr_ = &(HostDataSourceFactory::instance());
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
     }
     }
 
 
     /// @brief Destructor
     /// @brief Destructor
@@ -176,7 +72,7 @@ public:
     virtual ~MySqlHostDataSourceTest() {
     virtual ~MySqlHostDataSourceTest() {
         hdsptr_->rollback();
         hdsptr_->rollback();
         HostDataSourceFactory::destroy();
         HostDataSourceFactory::destroy();
-        destroySchema();
+        destroyMySQLSchema();
     }
     }
 
 
     /// @brief Reopen the database
     /// @brief Reopen the database
@@ -188,8 +84,8 @@ public:
     /// the same database.
     /// the same database.
     void reopen(Universe) {
     void reopen(Universe) {
         HostDataSourceFactory::destroy();
         HostDataSourceFactory::destroy();
-        HostDataSourceFactory::create(validConnectionString());
-        hdsptr_ = &(HostDataSourceFactory::instance());
+        HostDataSourceFactory::create(validMySQLConnectionString());
+        hdsptr_ = HostDataSourceFactory::getHostDataSourcePtr();
     }
     }
 
 
 };
 };
@@ -204,14 +100,14 @@ public:
 TEST(MySqlHostDataSource, OpenDatabase) {
 TEST(MySqlHostDataSource, OpenDatabase) {
 
 
     // Schema needs to be created for the test to work.
     // Schema needs to be created for the test to work.
-    destroySchema();
-    createSchema();
+    destroyMySQLSchema();
+    createMySQLSchema();
 
 
     // Check that lease manager open the database opens correctly and tidy up.
     // Check that lease manager open the database opens correctly and tidy up.
     //  If it fails, print the error message.
     //  If it fails, print the error message.
     try {
     try {
-        HostDataSourceFactory::create(validConnectionString());
-        EXPECT_NO_THROW((void) HostDataSourceFactory::instance());
+        HostDataSourceFactory::create(validMySQLConnectionString());
+        EXPECT_NO_THROW((void) HostDataSourceFactory::getHostDataSourcePtr());
         HostDataSourceFactory::destroy();
         HostDataSourceFactory::destroy();
     } catch (const isc::Exception& ex) {
     } catch (const isc::Exception& ex) {
         FAIL() << "*** ERROR: unable to open database, reason:\n"
         FAIL() << "*** ERROR: unable to open database, reason:\n"
@@ -222,7 +118,7 @@ TEST(MySqlHostDataSource, OpenDatabase) {
 
 
     // Check that attempting to get an instance of the lease manager when
     // Check that attempting to get an instance of the lease manager when
     // none is set throws an exception.
     // none is set throws an exception.
-    EXPECT_THROW(HostDataSourceFactory::instance(), NoHostDataSourceManager);
+    EXPECT_FALSE(HostDataSourceFactory::getHostDataSourcePtr());
 
 
     // Check that wrong specification of backend throws an exception.
     // Check that wrong specification of backend throws an exception.
     // (This is really a check on LeaseMgrFactory, but is convenient to
     // (This is really a check on LeaseMgrFactory, but is convenient to
@@ -254,7 +150,7 @@ TEST(MySqlHostDataSource, OpenDatabase) {
         NoDatabaseName);
         NoDatabaseName);
 
 
     // Tidy up after the test
     // Tidy up after the test
-    destroySchema();
+    destroyMySQLSchema();
 }
 }
 
 
 /// @brief Check conversion functions
 /// @brief Check conversion functions

+ 10 - 111
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc

@@ -20,6 +20,7 @@
 #include <dhcpsrv/mysql_lease_mgr.h>
 #include <dhcpsrv/mysql_lease_mgr.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/tests/test_utils.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <dhcpsrv/tests/mysql_schema.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -38,108 +39,6 @@ using namespace std;
 
 
 namespace {
 namespace {
 
 
-// This holds statements to create and destroy the schema.
-#include "schema_mysql_copy.h"
-
-// Connection strings.
-// Database: keatest
-// Host: localhost
-// Username: keatest
-// Password: keatest
-const char* VALID_TYPE = "type=mysql";
-const char* INVALID_TYPE = "type=unknown";
-const char* VALID_NAME = "name=keatest";
-const char* INVALID_NAME = "name=invalidname";
-const char* VALID_HOST = "host=localhost";
-const char* INVALID_HOST = "host=invalidhost";
-const char* VALID_USER = "user=keatest";
-const char* INVALID_USER = "user=invaliduser";
-const char* VALID_PASSWORD = "password=keatest";
-const char* INVALID_PASSWORD = "password=invalid";
-
-// Given a combination of strings above, produce a connection string.
-string connectionString(const char* type, const char* name, const char* host,
-                        const char* user, const char* password) {
-    const string space = " ";
-    string result = "";
-
-    if (type != NULL) {
-        result += string(type);
-    }
-    if (name != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(name);
-    }
-
-    if (host != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(host);
-    }
-
-    if (user != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(user);
-    }
-
-    if (password != NULL) {
-        if (! result.empty()) {
-            result += space;
-        }
-        result += string(password);
-    }
-
-    return (result);
-}
-
-// Return valid connection string
-string
-validConnectionString() {
-    return (connectionString(VALID_TYPE, VALID_NAME, VALID_HOST,
-                             VALID_USER, VALID_PASSWORD));
-}
-
-// @brief Clear everything from the database
-//
-// There is no error checking in this code: if something fails, one of the
-// tests will (should) fall over.
-void destroySchema() {
-    MySqlHolder mysql;
-
-    // Open database
-    (void) mysql_real_connect(mysql, "localhost", "keatest",
-                              "keatest", "keatest", 0, NULL, 0);
-
-    // Get rid of everything in it.
-    for (int i = 0; destroy_statement[i] != NULL; ++i) {
-        (void) mysql_query(mysql, destroy_statement[i]);
-    }
-}
-
-// @brief Create the Schema
-//
-// Creates all the tables in what is assumed to be an empty database.
-//
-// There is no error checking in this code: if it fails, one of the tests
-// will fall over.
-void createSchema() {
-    MySqlHolder mysql;
-
-    // Open database
-    (void) mysql_real_connect(mysql, "localhost", "keatest",
-                              "keatest", "keatest", 0, NULL, 0);
-
-    // Execute creation statements.
-    for (int i = 0; create_statement[i] != NULL; ++i) {
-        ASSERT_EQ(0, mysql_query(mysql, create_statement[i]))
-            << "Failed on statement " << i << ": " << create_statement[i];
-    }
-}
 
 
 /// @brief Test fixture class for testing MySQL Lease Manager
 /// @brief Test fixture class for testing MySQL Lease Manager
 ///
 ///
@@ -154,12 +53,12 @@ public:
     MySqlLeaseMgrTest() {
     MySqlLeaseMgrTest() {
 
 
         // Ensure schema is the correct one.
         // Ensure schema is the correct one.
-        destroySchema();
-        createSchema();
+        destroyMySQLSchema();
+        createMySQLSchema();
 
 
         // Connect to the database
         // Connect to the database
         try {
         try {
-            LeaseMgrFactory::create(validConnectionString());
+            LeaseMgrFactory::create(validMySQLConnectionString());
         } catch (...) {
         } catch (...) {
             std::cerr << "*** ERROR: unable to open database. The test\n"
             std::cerr << "*** ERROR: unable to open database. The test\n"
                          "*** environment is broken and must be fixed before\n"
                          "*** environment is broken and must be fixed before\n"
@@ -178,7 +77,7 @@ public:
     virtual ~MySqlLeaseMgrTest() {
     virtual ~MySqlLeaseMgrTest() {
         lmptr_->rollback();
         lmptr_->rollback();
         LeaseMgrFactory::destroy();
         LeaseMgrFactory::destroy();
-        destroySchema();
+        destroyMySQLSchema();
     }
     }
 
 
     /// @brief Reopen the database
     /// @brief Reopen the database
@@ -190,7 +89,7 @@ public:
     /// the same database.
     /// the same database.
     void reopen(Universe) {
     void reopen(Universe) {
         LeaseMgrFactory::destroy();
         LeaseMgrFactory::destroy();
-        LeaseMgrFactory::create(validConnectionString());
+        LeaseMgrFactory::create(validMySQLConnectionString());
         lmptr_ = &(LeaseMgrFactory::instance());
         lmptr_ = &(LeaseMgrFactory::instance());
     }
     }
 
 
@@ -206,13 +105,13 @@ public:
 TEST(MySqlOpenTest, OpenDatabase) {
 TEST(MySqlOpenTest, OpenDatabase) {
 
 
     // Schema needs to be created for the test to work.
     // Schema needs to be created for the test to work.
-    destroySchema();
-    createSchema();
+    destroyMySQLSchema();
+    createMySQLSchema();
 
 
     // Check that lease manager open the database opens correctly and tidy up.
     // Check that lease manager open the database opens correctly and tidy up.
     //  If it fails, print the error message.
     //  If it fails, print the error message.
     try {
     try {
-        LeaseMgrFactory::create(validConnectionString());
+        LeaseMgrFactory::create(validMySQLConnectionString());
         EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
         EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
         LeaseMgrFactory::destroy();
         LeaseMgrFactory::destroy();
     } catch (const isc::Exception& ex) {
     } catch (const isc::Exception& ex) {
@@ -256,7 +155,7 @@ TEST(MySqlOpenTest, OpenDatabase) {
         NoDatabaseName);
         NoDatabaseName);
 
 
     // Tidy up after the test
     // Tidy up after the test
-    destroySchema();
+    destroyMySQLSchema();
 }
 }
 
 
 /// @brief Check the getType() method
 /// @brief Check the getType() method

+ 131 - 0
src/lib/dhcpsrv/tests/mysql_schema.cc

@@ -0,0 +1,131 @@
+// Copyright (C) 2015 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 <config.h>
+#include <string>
+#include <mysql.h>
+#include <dhcpsrv/mysql_connection.h>
+#include <gtest/gtest.h>
+
+// This holds statements to create and destroy the schema.
+#include "schema_mysql_copy.h"
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// Connection strings.
+// Database: keatest
+// Host: localhost
+// Username: keatest
+// Password: keatest
+const char* VALID_TYPE = "type=mysql";
+const char* INVALID_TYPE = "type=unknown";
+const char* VALID_NAME = "name=keatest";
+const char* INVALID_NAME = "name=invalidname";
+const char* VALID_HOST = "host=localhost";
+const char* INVALID_HOST = "host=invalidhost";
+const char* VALID_USER = "user=keatest";
+const char* INVALID_USER = "user=invaliduser";
+const char* VALID_PASSWORD = "password=keatest";
+const char* INVALID_PASSWORD = "password=invalid";
+
+string connectionString(const char* type, const char* name, const char* host,
+                        const char* user, const char* password) {
+    const string space = " ";
+    string result = "";
+
+    if (type != NULL) {
+        result += string(type);
+    }
+    if (name != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(name);
+    }
+
+    if (host != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(host);
+    }
+
+    if (user != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(user);
+    }
+
+    if (password != NULL) {
+        if (! result.empty()) {
+            result += space;
+        }
+        result += string(password);
+    }
+
+    return (result);
+}
+
+// Return valid connection string
+string
+validMySQLConnectionString() {
+    return (connectionString(VALID_TYPE, VALID_NAME, VALID_HOST,
+                             VALID_USER, VALID_PASSWORD));
+}
+
+// @brief Clear everything from the database
+//
+// There is no error checking in this code: if something fails, one of the
+// tests will (should) fall over.
+void destroyMySQLSchema() {
+    MySqlHolder mysql;
+
+    // Open database
+    (void) mysql_real_connect(mysql, "localhost", "keatest",
+                              "keatest", "keatest", 0, NULL, 0);
+
+    // Get rid of everything in it.
+    for (int i = 0; destroy_statement[i] != NULL; ++i) {
+        (void) mysql_query(mysql, destroy_statement[i]);
+    }
+}
+
+// @brief Create the Schema
+//
+// Creates all the tables in what is assumed to be an empty database.
+//
+// There is no error checking in this code: if it fails, one of the tests
+// will fall over.
+void createMySQLSchema() {
+    MySqlHolder mysql;
+
+    // Open database
+    (void) mysql_real_connect(mysql, "localhost", "keatest",
+                              "keatest", "keatest", 0, NULL, 0);
+
+    // Execute creation statements.
+    for (int i = 0; create_statement[i] != NULL; ++i) {
+        ASSERT_EQ(0, mysql_query(mysql, create_statement[i]))
+            << "Failed on statement " << i << ": " << create_statement[i];
+    }
+}
+
+};
+};
+};

+ 69 - 0
src/lib/dhcpsrv/tests/mysql_schema.h

@@ -0,0 +1,69 @@
+// Copyright (C) 2015 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.
+
+#ifndef TEST_MYSQL_SCHEMA_H
+#define TEST_MYSQL_SCHEMA_H
+
+#include <config.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+extern const char* VALID_TYPE;
+extern const char* INVALID_TYPE;
+extern const char* VALID_NAME;
+extern const char* INVALID_NAME;
+extern const char* VALID_HOST;
+extern const char* INVALID_HOST;
+extern const char* VALID_USER;
+extern const char* INVALID_USER;
+extern const char* VALID_PASSWORD;
+extern const char* INVALID_PASSWORD;
+
+/// @brief Create the Schema
+///
+/// Creates all the tables in what is assumed to be an empty database.
+///
+/// There is no error checking in this code: if it fails, one of the tests
+/// will fall over.
+void createMySQLSchema();
+
+/// @brief Clear everything from the database
+///
+/// There is no error checking in this code: if something fails, one of the
+/// tests will (should) fall over.
+void destroyMySQLSchema();
+
+/// Return valid connection string
+///
+/// @return valid MySQL connection string.
+std::string validMySQLConnectionString();
+
+/// @brief Given a combination of strings above, produce a connection string.
+///
+/// @param type type of the database
+/// @param name name of the database to connect to
+/// @param host hostname
+/// @param user username used to authendicate during connection attempt
+/// @param password password used to authendicate during connection attempt
+/// @return string containing all specified parameters
+std::string connectionString(const char* type, const char* name, const char* host,
+                             const char* user, const char* password);
+};
+};
+};
+
+#endif