Browse Source

[5272] lease_cmds in progress

Tomek Mrugalski 7 years ago
parent
commit
a2924fcc11

+ 2 - 0
configure.ac

@@ -1630,6 +1630,8 @@ AC_CONFIG_FILES([Makefile
                  src/bin/shell/tests/shell_unittest.py
                  src/hooks/Makefile
                  src/hooks/dhcp/Makefile
+                 src/hooks/dhcp/lease_cmds/Makefile
+                 src/hooks/dhcp/lease_cmds/tests/Makefile
                  src/hooks/dhcp/user_chk/Makefile
                  src/hooks/dhcp/user_chk/tests/Makefile
                  src/hooks/dhcp/user_chk/tests/test_data_files_config.h

+ 1 - 1
src/hooks/dhcp/Makefile.am

@@ -1 +1 @@
-SUBDIRS = user_chk
+SUBDIRS = user_chk lease_cmds

+ 62 - 0
src/hooks/dhcp/lease_cmds/Makefile.am

@@ -0,0 +1,62 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS  = $(KEA_CXXFLAGS)
+
+# Define rule to build logging source files from message file
+lease_cmds_messages.h lease_cmds_messages.cc: s-messages
+s-messages: lease_cmds_messages.mes
+	$(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes
+	touch $@
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = lease_cmds_messages.h lease_cmds_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = lease_cmds_messages.mes
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda lease_cmds_messages.h lease_cmds_messages.cc s-messages
+
+# convenience archive
+
+noinst_LTLIBRARIES = liblease_cmds.la
+
+liblease_cmds_la_SOURCES  = lease_cmds.cc lease_cmds.h
+liblease_cmds_la_SOURCES += lease_parser.h lease_parser.cc
+liblease_cmds_la_SOURCES += lease_cmds_log.cc lease_cmds_log.h
+liblease_cmds_la_SOURCES += load_unload.cc
+liblease_cmds_la_SOURCES += version.cc
+
+nodist_liblease_cmds_la_SOURCES = lease_cmds_messages.cc lease_cmds_messages.h
+
+liblease_cmds_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblease_cmds_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+# install the shared object into $(libdir)/hooks
+lib_hooksdir = $(libdir)/hooks
+lib_hooks_LTLIBRARIES = libdhcp_lease_cmds.la
+
+libdhcp_lease_cmds_la_SOURCES  =
+libdhcp_lease_cmds_la_LDFLAGS  = $(AM_LDFLAGS)
+libdhcp_lease_cmds_la_LDFLAGS  += -avoid-version -export-dynamic -module
+libdhcp_lease_cmds_la_LIBADD  = liblease_cmds.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/eval/libkea-eval.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/stats/libkea-stats.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/util/threads/libkea-threads.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_lease_cmds_la_LIBADD  += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+
+EXTRA_DIST += lease_cmds.dox

+ 528 - 0
src/hooks/dhcp/lease_cmds/lease_cmds.cc

@@ -0,0 +1,528 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <lease_cmds.h>
+#include <config/command_mgr.h>
+#include <lease_parser.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <asiolink/io_address.h>
+#include <lease_cmds_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcp/duid.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <exceptions/exceptions.h>
+#include <boost/bind.hpp>
+#include <string>
+
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Wrapper class around reservation command handlers.
+class LeaseCmdsImpl {
+public:
+    LeaseCmdsImpl();
+
+    ~LeaseCmdsImpl();
+
+/// @brief Parameters specified for reservation-get and reservation-del
+///
+/// As both call types (get and delete) need specify which reservation to
+/// act on, they have the same set of parameters. In particular, those
+/// two call types support the following sets of parameters:
+/// - subnet-id, address
+/// - subnet-id, identifier type, identifier value
+///
+/// This class stores those parameters and is used to pass them around.
+class Parameters {
+public:
+
+    /// @brief specifies type of query (by IP addr, by hwaddr, by DUID)
+    typedef enum {
+        TYPE_ADDR,    ///< query by IP address (either v4 or v6)
+        TYPE_HWADDR,  ///< query by hardware address (v4 only)
+        TYPE_DUID     ///< query by DUID (v6 only)
+    } Type;
+    
+    /// @brief Specifies subnet-id (always used)
+    SubnetID subnet_id;
+
+    /// @brief Specifies IPv4 or IPv6 address (used when query_by_addr is true)
+    IOAddress addr;
+
+    /// @brief Specifies identifier type (usually FLEX_ID, used when
+    ///        query_by_addr is false)
+    HWAddrPtr hwaddr;
+
+    /// @brief Specifies identifier value (used when query_by_addr is false)
+    isc::dhcp::DuidPtr duid;
+
+    /// @brief specifies parameter types (true = query by address, false =
+    ///         query by indetifier-type,identifier)
+    Type query_type;
+
+    /// @brief Default contstructor.
+    Parameters()
+        :addr("::"), query_type(TYPE_ADDR) {
+    }
+};
+
+private:
+
+    /// @brief Registers commands:
+    ///
+    /// Registers:
+    /// - lease4-add
+    /// - lease6-add
+    /// - lease4-get
+    /// - lease6-get
+    /// - lease4-del
+    /// - lease6-del
+    /// - lease4-update
+    /// - lease6-update
+    /// - lease4-del-all
+    /// - lease6-del-all
+    
+    /// @throw Unexpected if CommandMgr is not available (should not happen)
+    void registerCommands();
+
+    /// @brief Dergisters commands:
+    ///
+    /// Deregisters:
+    /// - lease4-add
+    /// - lease6-add
+    /// - lease4-get
+    /// - lease6-get
+    /// - lease4-del
+    /// - lease6-del
+    /// - lease4-update
+    /// - lease6-update
+    /// - lease4-del-all
+    /// - lease6-del-all
+    ///
+    /// @throw Unexpected if CommandMgr is not available (should not happen)
+    void deregisterCommands();
+
+    /// @brief reservation-add command handler
+    ///
+    /// This command attempts to add a lease.
+    ///
+    /// An example full command looks as follows. Note that the args
+    /// parameter is expected to contain the "arguments" portion of it.
+    /// This function covers v4 lease only.
+    ///
+    /// Example command
+    /// {
+    ///     "command": "lease4-add",
+    ///     "parameters": {
+    ///         "address": "192.0.2.1",
+    ///         "hwaddr": "00:01:02:03:04:05",
+    ///         "client-id": "this-is-a-client",
+    ///         "valid-lft": 3600,
+    ///         "expire": 1499282530,
+    ///         "subnet-id": 1,
+    ///         "fdqn_fwd": true,
+    ///         "fqdn_rev": true,
+    ///         "hostname": "myhost.example.org",
+    ///         "state": 0
+    ///     }
+    /// }
+    ///
+    /// @param command should be 'reservation-add' (but it's ignored)
+    /// @param args must contain host reservation definition.
+    /// @return result of the operation
+    static ConstElementPtr
+    leaseAddHandler(const string& command, ConstElementPtr args);
+
+    /// @brief reservation-get command handler
+    ///
+    /// This command attempts to retrieve a host that match selected criteria.
+    /// Two types of parameters are supported: (subnet-id, address) or
+    /// (subnet-id, identifier-type, identifier).
+    ///
+    /// Example command for query by (subnet-id, address):
+    /// {
+    ///     "command": "reservation-get",
+    ///     "arguments": {
+    ///         "subnet-id": 1,
+    ///         "ip-address": "192.0.2.202"
+    ///     }
+    /// }
+    ///
+    /// Example command for query by (subnet-id, identifier-type, identifier)
+    /// {
+    ///     "command": "reservation-get",
+    ///     "arguments": {
+    ///         "subnet-id": 1,
+    ///         "identifier-type": "hw-address",
+    ///         "identifier": "00:01:02:03:04:05"
+    ///     }
+    /// }";
+    /// @param command should be 'reservation-get' (but it's ignored)
+    /// @param args must contain host reservation definition.
+    /// @return result of the operation (host will be included as parameters, if found)
+    static ConstElementPtr
+    leaseGetHandler(const string& command, ConstElementPtr args);
+
+    /// @brief reservation-del command handler
+    ///
+    /// This command attempts to delete a host that match selected criteria.
+    /// Two types of parameters are supported: (subnet-id, address) or
+    /// (subnet-id, identifier-type, identifier).
+    ///
+    /// Note: for this operation to work, hosts-database must be specified
+    /// in your configuration file (or from code point of view, alternate_source_
+    /// must be set in HostMgr).
+    ///
+    /// Example command for query by (subnet-id, address):
+    /// {
+    ///     "command": "reservation-get",
+    ///     "arguments": {
+    ///         "subnet-id": 1,
+    ///         "ip-address": "192.0.2.202"
+    ///     }
+    /// }
+    ///
+    /// Example command for query by (subnet-id, identifier-type, identifier)
+    /// {
+    ///     "command": "reservation-get",
+    ///     "arguments": {
+    ///         "subnet-id": 1,
+    ///         "identifier-type": "hw-address",
+    ///         "identifier": "00:01:02:03:04:05"
+    ///     }
+    /// }";
+    /// @param command should be 'reservation-add' (but it's ignored)
+    /// @param args must contain host reservation definition.
+    /// @return result of the operation (host will be included as parameters, if found)
+    static ConstElementPtr
+    leaseDelHandler(const string& command, ConstElementPtr args);
+
+    static ConstElementPtr
+    leaseUpdateHandler(const string& command, ConstElementPtr args);
+
+    static ConstElementPtr
+    leaseWipeHandler(const string& command, ConstElementPtr args);
+    
+    /// @brief Extracts parameters required for reservation-get and reservation-del
+    ///
+    /// See @ref Parameters class for detailed description of what is expected
+    /// in the args structure.
+    ///
+    /// @param args - arguments passed to command
+    /// @return parsed parameters
+    /// @throw BadValue if input arguments don't make sense.
+    static Parameters getParameters(const ConstElementPtr& args);
+
+    /// @brief Covenience pointer used to access database storage
+    static HostDataSourcePtr db_storage_;
+
+    /// @brief Protocol family (IPv4 or IPv6)
+    static uint16_t family_;
+};
+
+LeaseCmdsImpl::LeaseCmdsImpl() {
+    family_ = CfgMgr::instance().getFamily();
+
+    registerCommands();
+}
+
+LeaseCmdsImpl::~LeaseCmdsImpl() {
+    deregisterCommands();
+}
+
+void LeaseCmdsImpl::registerCommands() {
+    /// @todo: Use registration mechanism once #5321 discussion is done
+    CommandMgr::instance().registerCommand("lease4-add",
+        boost::bind(&LeaseCmdsImpl::leaseAddHandler, _1, _2));
+    CommandMgr::instance().registerCommand("lease6-add",
+        boost::bind(&LeaseCmdsImpl::leaseAddHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("lease4-get",
+        boost::bind(&LeaseCmdsImpl::leaseGetHandler, _1, _2));
+    CommandMgr::instance().registerCommand("lease6-get",
+        boost::bind(&LeaseCmdsImpl::leaseGetHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("lease4-del",
+    boost::bind(&LeaseCmdsImpl::leaseDelHandler, _1, _2));
+    CommandMgr::instance().registerCommand("lease6-del",
+    boost::bind(&LeaseCmdsImpl::leaseDelHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("lease4-update",
+    boost::bind(&LeaseCmdsImpl::leaseUpdateHandler, _1, _2));
+    CommandMgr::instance().registerCommand("lease6-update",
+    boost::bind(&LeaseCmdsImpl::leaseUpdateHandler, _1, _2));
+
+    CommandMgr::instance().registerCommand("lease4-del-all",
+    boost::bind(&LeaseCmdsImpl::leaseWipeHandler, _1, _2));
+    CommandMgr::instance().registerCommand("lease6-del-all",
+    boost::bind(&LeaseCmdsImpl::leaseWipeHandler, _1, _2));
+}
+
+void LeaseCmdsImpl::deregisterCommands() {
+    /// @todo: Use deregistration mechanism once #5321 discussion is done
+    CommandMgr::instance().deregisterCommand("lease4-add");
+    CommandMgr::instance().deregisterCommand("lease6-add");
+
+    CommandMgr::instance().deregisterCommand("lease4-get");
+    CommandMgr::instance().deregisterCommand("lease6-get");
+
+    CommandMgr::instance().deregisterCommand("lease4-del");
+    CommandMgr::instance().deregisterCommand("lease6-del");
+
+    CommandMgr::instance().deregisterCommand("lease4-update");
+    CommandMgr::instance().deregisterCommand("lease6-update");
+
+    CommandMgr::instance().deregisterCommand("lease4-del-all");
+    CommandMgr::instance().deregisterCommand("lease6-del-all");
+}
+
+ConstElementPtr
+LeaseCmdsImpl::leaseAddHandler(const std::string& name,
+                               ConstElementPtr params) {
+    bool v4 = (name == "lease4-add");
+    
+    string txt = "(missing parameters)";
+    if (params) {
+        txt = params->str();
+    }
+
+    try {
+        if (!params) {
+            isc_throw(isc::BadValue, "no parameters specified for the command");
+        }
+
+        ConstElementPtr lease_data = params->get("lease");
+        if (!lease_data) {
+            isc_throw(isc::BadValue, "'lease' parameters must be specified");
+        }
+
+        Lease4Ptr lease4;
+        Lease6Ptr lease6;
+        if (v4) {
+            LeaseDataParser4 parser;
+            lease4 = parser.parse(lease_data);
+
+            checkLeaseIntegrity(lease4);
+
+            if (lease4) {
+                LeaseMgrFactory::instance().add(lease4);
+            }
+            
+        } else {
+            LeaseDataParser6 parser;
+            lease6 = parser.parse(lease_data);
+
+            checkLeaseIntegrity(lease6);
+
+            if (lease6) {
+                LeaseMgrFactory::instance().add(lease6);
+            }
+        }
+
+
+    } catch (const std::exception& ex) {
+        LOG_ERROR(lease_cmds_logger, LEASE_CMDS_RESERV_ADD_FAILED)
+            .arg(txt)
+            .arg(ex.what());
+        return (createAnswer(CONTROL_RESULT_ERROR, ex.what()));
+    }
+    LOG_INFO(lease_cmds_logger, v4?LEASE_CMDS_ADD4 : LEASE_CMDS_ADD6).arg(txt);
+    return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease added."));
+}
+
+LeaseCmdsImpl::Parameters
+LeaseCmdsImpl::getParameters(const ConstElementPtr& params) {
+    Parameters x;
+
+    if (!params || params->getType() != Element::map) {
+        isc_throw(BadValue, "Parameters missing or are not a map.");
+    }
+
+    // We support 2 sets of parameters for lease-get/lease-del:
+    // lease-get(subnet-id, address)
+    // lease-get(subnet-id, interifier-type, identifier)
+
+    ConstElementPtr tmp = params->get("subnet-id");
+    if (!tmp) {
+        isc_throw(BadValue, "Mandatory 'subnet-id' parameter missing.");
+    }
+    if (tmp->getType() != Element::integer) {
+        isc_throw(BadValue, "'subnet-id' parameter is not integer.");
+    }
+    x.subnet_id = tmp->intValue();
+
+    tmp = params->get("ip-address");
+    ConstLeasePtr host;
+    if (tmp) {
+        if (tmp->getType() != Element::string) {
+            isc_throw(BadValue, "'ip-address' is not a string.");
+        }
+
+        x.addr = IOAddress(tmp->stringValue());
+        x.query_by_addr = true;
+        return (x);
+    }
+
+    // No address specified. Ok, so it must be identifier based query.
+    // "identifier-type": "duid",
+    // "identifier": "aa:bb:cc:dd:ee:..."
+
+    ConstElementPtr type = params->get("identifier-type");
+    ConstElementPtr ident = params->get("identifier");
+    if (!type || type->getType() != Element::string) {
+        isc_throw(BadValue, "No 'ip-address' provided"
+                  " and 'identifier-type' is either missing or not a string.");
+    }
+    if (!ident || ident->getType() != Element::string) {
+        isc_throw(BadValue, "No 'ip-address' provided"
+                  " and 'identifier' is either missing or not a string.");
+    }
+
+    // Got the parameters. Let's see if their values make sense.
+
+    // Try to parse the identifier value first.
+    try {
+        x.ident = util::str::quotedStringToBinary(ident->stringValue());
+        if (x.ident.empty()) {
+            util::str::decodeFormattedHexString(ident->stringValue(),
+                                                x.ident);
+        }
+    } catch (...) {
+        // The string doesn't match any known pattern, so we have to
+        // report an error at this point.
+        isc_throw(BadValue, "Unable to parse 'identifier' value.");
+    }
+
+    if (x.ident.empty()) {
+        isc_throw(BadValue, "Unable to query for empty 'identifier'.");
+    }
+
+    // Next, try to convert identifier-type
+    try {
+        x.type = Lease::getIdentifierType(type->stringValue());
+    } catch (const std::exception& ex) {
+        isc_throw(BadValue, "Value of 'identifier-type' was not recognized.");
+    }
+
+    x.query_by_addr = false;
+    return (x);
+}
+
+ConstElementPtr
+LeaseCmdsImpl::leaseGetHandler(const std::string& name, ConstElementPtr params) {
+    Parameters p;
+    Lease4Ptr lease4;
+    Lease6Ptr lease6;
+    bool v4 = (name == "lease4-get");
+    try {
+        p = getParameters(params);
+
+        switch (p.query_type) {
+        case TYPE_ADDR: {
+            // Query by address
+            if (v4) {
+                lease4 = LeaseMgr::instance().getLease4(p.subnet_id, p.addr);
+            } else {
+                lease6 = LeaseMgr::instance().getLease6(p.subnet_id, p.addr);
+            }
+            break;
+        }
+        case TYPE_HWADDR:
+            if (v4) {
+                lease4 = LeaseMgr::instance().getLease4(p.subnet_id, p.hwaddr);
+            } else {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "Query by hw-address is not allowed in v6."));
+            }
+        case TYPE_DUID:
+            if (v6) {
+                lease6 = LeaseMgr::instance().getLease6(p.subnet_id, p.duid);
+            } else {
+                return (createAnswer(CONTROL_RESULT_ERROR,
+                                     "Query by duid is not allowed in v4."));
+            }
+        default:
+            return (createAnswer(CONTROL_RESULT_ERROR,
+                                 "Unknown query type: " << static_cast<int>(p.query_type)));
+        }
+    } catch (const std::exception& ex) {
+        return (createAnswer(CONTROL_RESULT_ERROR,
+                             "Failure during leaseX-get: " << ex.what()));
+    }
+
+    ElementPtr lease_json;
+    if (v4 && lease4) {
+        lease_json = lease4->toElement4();
+    }
+    if (!v4 && lease6) {
+        lease_json = lease6->toElement6();
+        
+    }
+    if (lease) {
+        return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease found.", lease_json));
+    } else {
+        return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease not found."));
+    }
+}
+
+ConstElementPtr
+LeaseCmdsImpl::reservationDelHandler(const std::string& /*name*/,
+                                    ConstElementPtr params) {
+    Parameters p;
+    bool deleted;
+    try {
+        p = getParameters(params);
+
+        if (p.query_by_addr) {
+            // try to delete by address
+            deleted = LeaseMgr::instance().del(p.subnet_id, p.addr);
+        } else {
+            // try to delete by identifier
+            if (family_ == AF_INET) {
+                deleted = LeaseMgr::instance().del4(p.subnet_id, p.type,
+                                                   &p.ident[0], p.ident.size());
+            } else {
+                deleted = LeaseMgr::instance().del6(p.subnet_id, p.type,
+                                                   &p.ident[0], p.ident.size());
+            }
+        }
+    } catch (const std::exception& ex) {
+        return (createAnswer(CONTROL_RESULT_ERROR, ex.what()));
+    }
+
+    if (deleted) {
+        return (createAnswer(CONTROL_RESULT_SUCCESS, "Lease deleted."));
+    } else {
+        return (createAnswer(CONTROL_RESULT_ERROR,
+                             "Lease not deleted (not found)."));
+    }
+}
+
+uint16_t LeaseCmdsImpl::family_ = AF_INET;
+LeaseDataSourcePtr LeaseCmdsImpl::db_storage_;
+
+LeaseCmds::LeaseCmds()
+    :impl_(new LeaseCmdsImpl()) {
+}
+
+LeaseCmds::~LeaseCmds() {
+    impl_.reset();
+}
+
+};
+};

+ 69 - 0
src/hooks/dhcp/lease_cmds/lease_cmds.h

@@ -0,0 +1,69 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LEASE_CMDS_H
+#define LEASE_CMDS_H
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Forward declaration of implementation class.
+class LeaseCmdsImpl;
+
+/// @brief A wrapper class that provides convenient initialization to the library.
+///
+/// This is a wrapper class that simply registers extra commands when
+/// instantiated and deregisters them when the instance is destryed.
+///
+/// For an actual implementation, see @ref LeaseCmdsImpl class in lease_cmds.cc file.
+class LeaseCmds {
+public:
+
+    /// @brief Initalizes additional host commands.
+    ///
+    /// First, it ensures that either alternate host data source or CfgHosts
+    /// (configuration storage) are available. Then it checks that CommandMgr
+    /// is available. Then finally, it registers the following commands:
+    /// - lease4-add
+    /// - lease6-add
+    /// - lease4-get
+    /// - lease6-get
+    /// - lease4-del
+    /// - lease6-del
+    /// - lease4-update
+    /// - lease6-update
+    /// - lease4-del-all
+    /// - lease6-del-all
+    ///
+    /// @throw Unexpected If any of the above fails.
+    LeaseCmds();
+
+    /// @brief Destructor
+    ///
+    /// - lease4-add
+    /// - lease6-add
+    /// - lease4-get
+    /// - lease6-get
+    /// - lease4-del
+    /// - lease6-del
+    /// - lease4-update
+    /// - lease6-update
+    /// - lease4-del-all
+    /// - lease6-del-all
+    /// Unregisters commands:
+    ~LeaseCmds();
+private:
+
+    /// Pointer to the actual implementation
+    boost::shared_ptr<LeaseCmdsImpl> impl_;
+};
+
+};
+};
+
+#endif

+ 16 - 0
src/hooks/dhcp/lease_cmds/lease_cmds_log.cc

@@ -0,0 +1,16 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <lease_cmds_log.h>
+
+namespace isc {
+namespace lease_cmds {
+
+isc::log::Logger lease_cmds_logger("lease_cmds_hooks");
+
+}
+}
+

+ 23 - 0
src/hooks/dhcp/lease_cmds/lease_cmds_log.h

@@ -0,0 +1,23 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LEASE_CMD_LOG_H
+#define LEASE_CMD_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <lease_cmds_messages.h>
+
+namespace isc {
+namespace lease_cmds {
+
+extern isc::log::Logger lease_cmds_logger;
+
+} // end of isc::lease_cmds
+} // end of isc namespace
+
+
+#endif

+ 51 - 0
src/hooks/dhcp/lease_cmds/lease_cmds_messages.mes

@@ -0,0 +1,51 @@
+# Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+
+% LEASE_CMDS_INIT_FAILED loading Lease Commands hooks library failed: %1
+This error message indicates an error during loading the Lease Commands
+hooks library. The details of the error are provided as argument of
+the log message.
+
+% LEASE_CMDS_INIT_OK loading Lease Commands hooks library successful
+This info message indicates that the Lease Commands hooks library has been
+loaded successfully. Enjoy!
+
+% LEASE_CMDS_DEINIT_FAILED unloading Lease Commands hooks library failed: %1
+This error message indicates an error during unloading the Lease Commands
+hooks library. The details of the error are provided as argument of
+the log message.
+
+% LEASE_CMDS_DEINIT_OK unloading Lease Commands hooks library successful
+This info message indicates that the Lease Commands hooks library has been
+removed successfully.
+
+% LEASE_CMDS_ADD4_FAILED Lease4-add command failed (parameters: %1, reason: %2)
+The lease4-add command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% LEASE_CMDS_ADD6_FAILED Lease4-add command failed (parameters: %1, reason: %2)
+The lease6-add command has failed. Both the reason as well as the
+parameters passed are logged.
+
+% LEASE_CMDS_ADD4 lease4-add command successful (parameters: %1)
+The lease4-add command has been successful. Parameters of the host
+added are logged.
+
+% LEASE_CMDS_ADD6 lease6-add command successful (parameters: %1)
+The lease6-add command has been successful. Parameters of the host
+added are logged.
+
+% LEASE_CMDS_DEL4_FAILED lease4-del command failed (parameters: %1, reason: %2)
+The attempt to delete an IPv4 lease (lease4-del command) has failed. Both the
+reason as well as the parameters passed are logged.
+
+% LEASE_CMDS_DEL6_FAILED lease6-del command failed (parameters: %1, reason: %2)
+The attempt to delete an IPv6 lease (lease4-del command) has failed. Both the
+reason as well as the parameters passed are logged.
+
+% LEASE_CMDS_DEL4 lease4-del command successful (parameters: %1)
+The attempt to delete an IPv4 lease (lease4-del command) has been successful.
+Parameters of the host removed are logged.
+
+% LEASE_CMDS_DEL6 lease4-del command successful (parameters: %1)
+The attempt to delete an IPv4 lease (lease4-del command) has been successful.
+Parameters of the host removed are logged.

+ 17 - 0
src/hooks/dhcp/lease_cmds/lease_parser.cc

@@ -0,0 +1,17 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+// V4:
+// address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state
+
+// V6:
+// address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,state
+
+#include <config.h>
+
+
+    

+ 37 - 0
src/hooks/dhcp/lease_cmds/lease_parser.h

@@ -0,0 +1,37 @@
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef LEASE_PARSER_H
+#define LEASE_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/lease.h>
+
+namespace isc {
+namespace lease_cmds {
+
+/// @brief Parser for a single Lease information.
+class LeaseParser : public isc::data::SimpleParser {
+};
+
+class Lease4Parser : public LeaseParser {
+public:
+    virtual isc::dhcp::Lease4Ptr parse(const isc::data::ConstElementPtr& lease_info);
+    virtual ~Lease4Parser() {}
+};
+
+class Lease6Parser : public LeaseParser {
+public:
+    virtual isc::dhcp::Lease6Ptr parse(const isc::data::ConstElementPtr& lease_info);
+    virtual ~Lease6Parser() {}
+};
+    
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
+    

+ 63 - 0
src/hooks/dhcp/lease_cmds/load_unload.cc

@@ -0,0 +1,63 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @file load_unload.cc Defines the load and unload hooks library functions.
+
+#include <config.h>
+#include <lease_cmds.h>
+#include <lease_cmds_log.h>
+#include <string>
+#include <hooks/hooks.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc::hooks;
+using namespace isc::lease_cmds;
+
+boost::shared_ptr<LeaseCmds> instance;
+
+extern "C" {
+
+/// @brief This function is called when the library is loaded.
+///
+/// This function creates LeaseCmds object that registers
+/// additional commands.
+///
+/// @param handle library handle (ignored)
+/// @return 0 when initialization is successful, 1 otherwise
+int load(LibraryHandle& /*handle*/) {
+
+    try {
+        instance.reset(new LeaseCmds());
+    } catch (const isc::Unexpected& ex) {
+        LOG_ERROR(lease_cmds_logger, LEASE_CMDS_INIT_FAILED)
+            .arg(ex.what());
+        return (1);
+    }
+
+    LOG_INFO(lease_cmds_logger, LEASE_CMDS_INIT_OK);
+    return (0);
+}
+
+/// @brief This function is called when the library is unloaded.
+///
+/// This function creates LeaseCmds object that deregisters
+/// additional commands.
+///
+/// @return 0 if deregistration was successful, 1 otherwise
+int unload() {
+    try {
+        instance.reset();
+    } catch (const isc::Unexpected& ex) {
+        LOG_ERROR(lease_cmds_logger, LEASE_CMDS_DEINIT_FAILED)
+            .arg(ex.what());
+        return (1);
+    }
+
+    LOG_INFO(lease_cmds_logger, LEASE_CMDS_DEINIT_OK);
+    return (0);
+}
+
+}

+ 64 - 0
src/hooks/dhcp/lease_cmds/tests/Makefile.am

@@ -0,0 +1,64 @@
+SUBDIRS = .
+
+# The 'parallel-tests' is the default only on recent automake versions.
+# However, since Kea 1.2.0 was released with files generated with automake version
+# that doesn't use it, we can't use parallel without rebuilding 1.2.0 tarballs.
+# We will flip this back to parallel-tests once 1.3.0 goes out with parallel
+# enabled.
+AUTOMAKE_OPTIONS = serial-tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/lease_cmds -I$(top_srcdir)/src/hooks/dhcp/lease_cmds
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DLIB_SO=\"$(abs_top_builddir)/src/hooks/dhcp/lease_cmds/.libs/libdhcp_lease_cmds.so\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+# Unit test data files need to get installed.
+EXTRA_DIST =
+
+CLEANFILES = *.gcno *.gcda
+
+# TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+LOG_COMPILER = $(LIBTOOL)
+AM_LOG_FLAGS = --mode=execute
+
+TESTS =
+if HAVE_GTEST
+TESTS += lease_cmds_unittests
+
+lease_cmds_unittests_SOURCES = run_unittests.cc
+lease_cmds_unittests_SOURCES += lease_cmds_unittest.cc
+
+lease_cmds_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+lease_cmds_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+lease_cmds_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# This is to workaround unused variables tcout and tcerr in
+# log4cplus's streams.h and unused parameters from some of the
+# Boost headers.
+lease_cmds_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+
+lease_cmds_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+lease_cmds_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+lease_cmds_unittests_LDADD += $(LOG4CPLUS_LIBS)
+lease_cmds_unittests_LDADD += $(CRYPTO_LIBS)
+lease_cmds_unittests_LDADD += $(BOOST_LIBS)
+lease_cmds_unittests_LDADD += $(GTEST_LDADD)
+endif
+noinst_PROGRAMS = $(TESTS)