Parcourir la source

[master] Added a hook point, "command-processed", to kea-dhcp4, kea-dhcp6 servers

    Merges in branch 'trac5111'
Thomas Markwalder il y a 7 ans
Parent
commit
043d17b068

+ 111 - 2
doc/guide/hooks.xml

@@ -462,8 +462,64 @@ hardware address: hwtype=1 08:00:2b:02:3f:4e, client-id: 17:34:e2:ff:09:92:54
 connected via relay at address: 192.2.16.33, identified by circuit-id:
 68:6f:77:64:79 and remote-id: 87:f6:79:77:ef
 </screen>
-          </para>
+        </para>
+        <para>
+        In addition to logging lease activity driven by DHCPv4 client traffic, it also
+        logs entries for the following lease management control channel commands:
+        lease4-add, lease4-update, and lease4-del.  Each entry is a single string
+        with no embedded end-of-line markers and they will typically have the following
+        forms:
+        </para>
+        <para>
+        <command>lease4-add:</command>
+<screen>
+Administrator added a lease of address: *address* to a device with hardware address: *device-id*
+</screen>
+        Dependent on the arguments of the add command, it may also include the
+        client-id and duration.
+        </para>
+        <para>
+        Example:
+<screen>
+Administrator added a lease of address: 192.0.2.202 to a device with hardware address: 1a:1b:1c:1d:1e:1f for 1 days 0 hrs 0 mins 0 secs
+</screen>
+        </para>
+        <para>
+        <command>lease4-update:</command>
+<screen>
+Administrator updated information on the lease of address: *address* to a device with hardware address: *device-id*
+</screen>
+        Dependent on the arguments of the update command, it may also include the
+        client-id and lease duration.
+        </para>
+        <para>
+        Example:
+<screen>
+Administrator updated information on the lease of address: 192.0.2.202 to a device with hardware address: 1a:1b:1c:1d:1e:1f, client-id: 1234567890
+</screen>
+        </para>
+        <para>
+        <command>lease4-del:</command>
+        Deletes have two forms, one by address and one by identifier and identifier type:
+<screen>
+Administrator deleted the lease for address: *address*
+</screen>
+        or
+<screen>
+Administrator deleted a lease for a device identified by: *identifier-type* of *identifier*
+</screen>
+        Currently only a type of @b hw-address (hardware address) is supported.
+        </para>
+        <para>
+        Examples:
+<screen>
+Administrator deleted the lease for address: 192.0.2.202
+
+Administrator deleted a lease for a device identified by: hw-address of 1a:1b:1c:1d:1e:1f
+</screen>
+        </para>
         </section>
+
         <section>
         <title>DHCPv6 Log Entries</title>
           <para>
@@ -515,7 +571,60 @@ DUID: 17:34:e2:ff:09:92:54 and hardware address: hwtype=1 08:00:2b:02:3f:4e
 link address: 3001::1, hop count: 1, identified by remote-id:
 01:02:03:04:0a:0b:0c:0d:0e:0f and subscriber-id: 1a:2b:3c:4d:5e:6f
 </screen>
-          </para>
+        </para>
+        <para>
+        In addition to logging lease activity driven by DHCPv6 client traffic, it also
+        logs entries for the following lease management control channel commands:
+        lease6-add, lease6-update, and lease6-del.  Each entry is a single string
+        with no embedded end-of-line markers and they will typically have the following
+        forms:
+        </para>
+        <para>
+        <command>lease6-add:</command>
+<screen>
+    Administrator added a lease of address: *address* to a device with DUID: *DUID*
+</screen>
+        Dependent on the arguments of the add command, it may also include the hardware address and duration.
+        </para>
+        <para>
+        Example:
+<screen>
+Administrator added a lease of address: 2001:db8::3 to a device with DUID: 1a:1b:1c:1d:1e:1f:20:21:22:23:24 for 1 days 0 hrs 0 mins 0 secs
+</screen>
+        </para>
+        <para>
+        <command>lease6-update:</command>
+<screen>
+Administrator updated information on the lease of address: *address* to a device with DUID: *DUID*
+</screen>
+        Dependent on the arguments of the update command, it may also include the hardware address and lease duration.
+        </para>
+        <para>
+        Example:
+<screen>
+Administrator updated information on the lease of address: 2001:db8::3 to a device with DUID: 1a:1b:1c:1d:1e:1f:20:21:22:23:24, hardware address: 1a:1b:1c:1d:1e:1f
+</screen>
+        </para>
+        <para>
+        <command>lease6-del:</command>
+        Deletes have two forms, one by address and one by identifier and identifier type:
+<screen>
+Administrator deleted the lease for address: *address*
+</screen>
+        or
+<screen>
+Administrator deleted a lease for a device identified by: *identifier-type* of *identifier*
+</screen>
+        Currently only a type of DUID is supported.
+        </para>
+        <para>
+Examples:
+<screen>
+Administrator deleted the lease for address: 2001:db8::3
+
+Administrator deleted a lease for a device identified by: duid of 1a:1b:1c:1d:1e:1f:20:21:22:23:24
+</screen>
+        </para>
         </section>
         <section id="forensic-log-configuration">
         <title>Configuring the Forensic Log Hooks</title>

+ 11 - 27
src/bin/dhcp4/dhcp4_hooks.dox

@@ -308,38 +308,22 @@ to the end of this list.
   expired leases will remain in the database and their recovery will
   be attempted during the next reclaim cycle.
 
-@subsection dhcpv4HooksControlCommandReceive control_command_receive
+@subsection dhcpv4HooksCommandProcessed command_processed
 
  - @b Arguments:
-   - name: @b command, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
+   - name: @b name, type: std::string, direction: <b>in</b>
+   - name: @b arguments type: isc::data::ConstElementPtr, direction: <b>in</b>
    - name: @b response, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
 
- - @b Description: this callout is executed when DHCPv4 server receives a
-   control command over the command channel (typically unix domain socket).
-   The "command" argument is a pointer to the parsed JSON structure
-   including command name and command arguments. If the callout implements
-   the specified command, it handles the command and creates appropriate
-   response. The response should be returned in the "response" argument.
-   In most cases, the callout which handles the command will set the next
-   step action to SKIP, to prevent the server from trying to handle the
-   command on its own and overriding the response created by the callouts.
-   A notable exception is the 'list-commands' command for which the callouts
-   should not set the next step action to SKIP. The server has a special
-   code path for this command which combines the list of commands returned
-   by the callouts with the list of commands supported by the server. If
-   the callout sets the next step action to SKIP in this case, the server
-   will only return the list of commands supported by the hook library.
-   The callout can modify the command arguments to influence the command
-   processing by the Command Manager. For example, it may freely modify
-   the configuration received in 'config-set' before it is processed by
-   the server. The SKIP action is not set in this case.
-
- - <b>Next step status</b>: if any callout sets the next step action to SKIP,
-   the server will assume that the command has been handled by the callouts
-   and will expect that the response is provided in the "response" argument.
-   The server will not handle the command in this case but simply return the
-   response returned by the callout to the caller.
+ - @b Description: this callout is executed after the DHCPv4 server receives
+   and processes a control command over the command channel (typically unix domain socket).
+   The "name" argument is the name of the command processed.
+   The "arguments" argument is a pointer to the parsed JSON structure
+   containing the command's input arguments.  The "response" argument
+   is the parsed JSON stucture containing the response generated by
+   the command processing.
 
+ - <b>Next step status</b>: Not applicable, it's value will be ignored.
 
 @section dhcpv4HooksOptionsAccess Accessing DHCPv4 Options within a Packet
 When the server constructs a response message to a client it includes

+ 14 - 30
src/bin/dhcp6/dhcp6_hooks.dox

@@ -350,38 +350,22 @@ to the end of this list.
   expired leases will remain in the database and their recovery will
   be attempted during the next reclaim cycle.
 
-@subsection dhcpv6HooksControlCommandReceive control_command_receive
+@subsection dhcpv6HooksCommandProcessed command_processed
 
  - @b Arguments:
-   - name: @b command, type: ConstElementPtr, direction: <b>in/out</b>
-   - name: @b response, type: ConstElementPtr, direction: <b>in/out</b>
-
- - @b Description: this callout is executed when DHCPv4 server receives a
-   control command over the command channel (typically unix domain socket).
-   The "command" argument is a pointer to the parsed JSON structure
-   including command name and command arguments. If the callout implements
-   the specified command, it handles the command and creates appropriate
-   response. The response should be returned in the "response" argument.
-   In most cases, the callout which handles the command will set the next
-   step action to SKIP, to prevent the server from trying to handle the
-   command on its own and overriding the response created by the callouts.
-   A notable exception is the 'list-commands' command for which the callouts
-   should not set the next step action to SKIP. The server has a special
-   code path for this command which combines the list of commands returned
-   by the callouts with the list of commands supported by the server. If
-   the callout sets the next step action to SKIP in this case, the server
-   will only return the list of commands supported by the hook library.
-   The callout can modify the command arguments to influence the command
-   processing by the Command Manager. For example, it may freely modify
-   the configuration received in 'config-set' before it is processed by
-   the server. The SKIP action is not set in this case.
-
- - <b>Next step status</b>: if any callout sets the next step action to SKIP,
-   the server will assume that the command has been handled by the callouts
-   and will expect that the response is provided in the "response" argument.
-   The server will not handle the command in this case but simply return the
-   response returned by the callout to the caller.
-
+   - name: @b name, type: std::string, direction: <b>in</b>
+   - name: @b arguments type: isc::data::ConstElementPtr, direction: <b>in</b>
+   - name: @b response, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed after the DHCPv6 server receives
+   and processes a control command over the command channel (typically unix domain socket).
+   The "name" argument is the name of the command processed.
+   The "arguments" argument is a pointer to the parsed JSON structure
+   containing the command's input arguments.  The "response" argument
+   is the parsed JSON stucture containing the response generated by
+   the command processing.
+
+ - <b>Next step status</b>: Not applicable, it's value will be ignored.
 
 @section dhcpv6HooksOptionsAccess Accessing DHCPv6 Options within a Packet
 When the server constructs a response message to a client it includes

+ 46 - 1
src/lib/config/base_command_mgr.cc

@@ -7,9 +7,32 @@
 #include <cc/command_interpreter.h>
 #include <config/base_command_mgr.h>
 #include <config/config_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
 #include <boost/bind.hpp>
 
 using namespace isc::data;
+using namespace isc::hooks;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct BaseCommandMgrHooks {
+    int hook_index_command_processed_; ///< index for "command_processe" hook point
+
+    /// Constructor that registers hook points for AllocationEngine
+    BaseCommandMgrHooks() {
+        hook_index_command_processed_ = HooksManager::registerHook("command_processed");
+    }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+BaseCommandMgrHooks Hooks;
+
+}; // anonymous namespace
 
 namespace isc {
 namespace config {
@@ -98,7 +121,29 @@ BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
 
         LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
 
-        return (handleCommand(name, arg, cmd));
+        ConstElementPtr response = handleCommand(name, arg, cmd);
+
+        // If there any callouts for command-processed hook point call them
+        if (HooksManager::calloutsPresent(Hooks.hook_index_command_processed_)) {
+            // Commands are not associated with anything so there's no pre-existing
+            // callout.
+            CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+            // Add the command name, arguments, and response to the callout context
+            callout_handle->setArgument("name", name);
+            callout_handle->setArgument("arguments", arg);
+            callout_handle->setArgument("response", response);
+
+            // Call callouts
+            HooksManager::callCallouts(Hooks.hook_index_command_processed_,
+                                        *callout_handle);
+
+            // Refresh the response from the callout context in case it was modified.
+            // @todo Should we allow this?
+            callout_handle->getArgument("response", response);
+        }
+
+        return (response);
 
     } catch (const Exception& e) {
         LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());

+ 3 - 0
src/lib/config/base_command_mgr.h

@@ -97,6 +97,7 @@ public:
 
     /// @brief Constructor.
     ///
+    /// Registers hookpoint "command-processed"
     /// Registers "list-commands" command.
     BaseCommandMgr();
 
@@ -107,6 +108,8 @@ public:
     ///
     /// This method processes specified command. The command is specified using
     /// a single Element. See @ref BaseCommandMgr for description of its syntax.
+    /// After the command has been handled, callouts for the hook point,
+    /// "command-processed" will be invoked.
     ///
     /// @param cmd Pointer to the data element representing command in JSON
     /// format.

+ 119 - 14
src/lib/config/tests/command_mgr_unittests.cc

@@ -36,6 +36,9 @@ public:
         handler_name_ = "";
         handler_params_ = ElementPtr();
         handler_called_ = false;
+        callout_name_ = "";
+        callout_argument_names_.clear();
+        std::string processed_log_ = "";
 
         CommandMgr::instance().deregisterAll();
         CommandMgr::instance().closeCommandSocket();
@@ -90,23 +93,14 @@ public:
         return (createAnswer(123, "test error message"));
     }
 
-    /// @brief A simple command handler used from within hook library.
-    ///
-    /// @param name Command name.
-    /// @param params Command arguments.
-    static ConstElementPtr my_hook_handler(const std::string& /*name*/,
-                                           const ConstElementPtr& /*params*/) {
-        return (createAnswer(234, "text generated by hook handler"));
-    }
-
     /// @brief Test callback which stores callout name and passed arguments and
     /// which handles the command.
     ///
     /// @param callout_handle Handle passed by the hooks framework.
     /// @return Always 0.
     static int
-    control_command_receive_handle_callout(CalloutHandle& callout_handle) {
-        callout_name_ = "control_command_receive_handle";
+    hook_lib_callout(CalloutHandle& callout_handle) {
+        callout_name_ = "hook_lib_callout";
 
         ConstElementPtr command;
         callout_handle.getArgument("command", command);
@@ -124,6 +118,35 @@ public:
         return (0);
     }
 
+    /// @brief Test callback which stores callout name and passed arguments and
+    /// which handles the command.
+    ///
+    /// @param callout_handle Handle passed by the hooks framework.
+    /// @return Always 0.
+    static int
+    command_processed_callout(CalloutHandle& callout_handle) {
+        callout_name_ = "command_processed_handler";
+
+        std::string name;
+        callout_handle.getArgument("name", name);
+
+        ConstElementPtr arguments;
+        callout_handle.getArgument("arguments", arguments);
+
+        ConstElementPtr response;
+        callout_handle.getArgument("response", response);
+        std::ostringstream os;
+        os << name << ":" << arguments->str() << ":" << response->str();
+        processed_log_ = os.str();
+
+        if (name == "change-response") {
+            callout_handle.setArgument("response",
+                createAnswer(777, "replaced response text"));
+        }
+
+        return (0);
+    }
+
     /// @brief IO service used by these tests.
     IOServicePtr io_service_;
 
@@ -141,6 +164,9 @@ public:
 
     /// @brief Holds a list of arguments passed to the callout.
     static std::vector<std::string> callout_argument_names_;
+
+    /// @brief Holds the generated command process log
+    static std::string processed_log_;
 };
 
 /// Name passed to the handler (used in my_handler)
@@ -155,6 +181,9 @@ bool CommandMgrTest::handler_called_(false);
 /// Holds invoked callout name.
 std::string CommandMgrTest::callout_name_("");
 
+/// @brief Holds the generated command process log
+std::string CommandMgrTest::processed_log_;
+
 /// Holds a list of arguments passed to the callout.
 std::vector<std::string> CommandMgrTest::callout_argument_names_;
 
@@ -317,7 +346,7 @@ TEST_F(CommandMgrTest, delegateProcessCommand) {
     // Register callout so as we can check that it is called before
     // processing the command by the manager.
     HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
-        "my-command", control_command_receive_handle_callout);
+        "my-command", hook_lib_callout);
 
     // Install local handler
     EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
@@ -343,7 +372,7 @@ TEST_F(CommandMgrTest, delegateProcessCommand) {
     ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
     EXPECT_EQ(234, status_code);
 
-    EXPECT_EQ("control_command_receive_handle", callout_name_);
+    EXPECT_EQ("hook_lib_callout", callout_name_);
 
     // Check that the appropriate arguments have been set. Include the
     // 'response' which should have been set by the callout.
@@ -358,7 +387,7 @@ TEST_F(CommandMgrTest, delegateListCommands) {
     // Register callout so as we can check that it is called before
     // processing the command by the manager.
     HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
-        "my-command", control_command_receive_handle_callout);
+        "my-command", hook_lib_callout);
 
     // Create my-command-bis which is unique for the local Command Manager,
     // i.e. not supported by the hook library. This command should also
@@ -435,3 +464,79 @@ TEST_F(CommandMgrTest, unixCreateTooLong) {
     EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
                  SocketError);
 }
+
+// This test verifies that a registered callout for the command_processed
+// hookpoint is invoked and passed the correct information.
+TEST_F(CommandMgrTest, commandProcessedHook) {
+    // Register callout so as we can check that it is called before
+    // processing the command by the manager.
+    HooksManager::preCalloutsLibraryHandle().registerCallout(
+        "command_processed", command_processed_callout);
+
+    // Install local handler
+    EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+                                                           my_handler));
+
+    // Now tell CommandMgr to process a command 'my-command' with the
+    // specified parameter.
+    ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+    ConstElementPtr command = createCommand("my-command", my_params);
+    ConstElementPtr answer;
+    ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+    // There should be an answer.
+    ASSERT_TRUE(answer);
+
+    // Local handler should be called
+    ASSERT_TRUE(handler_called_);
+
+    // Verify that the response came through intact
+    EXPECT_EQ("{ \"result\": 123, \"text\": \"test error message\" }",
+              answer->str());
+
+    // Make sure invoked the command_processed callout
+    EXPECT_EQ("command_processed_handler", callout_name_);
+
+    // Verify the callout could extract all the context arguments
+    EXPECT_EQ("my-command:[ \"just\", \"some\", \"data\" ]:"
+              "{ \"result\": 123, \"text\": \"test error message\" }",
+              processed_log_);
+}
+
+// This test verifies that a registered callout for the command_processed
+// hookpoint is invoked and can replace the command response content.
+TEST_F(CommandMgrTest, commandProcessedHookReplaceResponse) {
+    // Register callout so as we can check that it is called before
+    // processing the command by the manager.
+    HooksManager::preCalloutsLibraryHandle().registerCallout(
+        "command_processed", command_processed_callout);
+
+    // Install local handler
+    EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+                                                           my_handler));
+
+    // Now tell CommandMgr to process a command 'my-command' with the
+    // specified parameter.
+    ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+    ConstElementPtr command = createCommand("change-response", my_params);
+    ConstElementPtr answer;
+    ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+    // There should be an answer.
+    ASSERT_TRUE(answer);
+
+    // Local handler should not have been called, command isn't recognized
+    ASSERT_FALSE(handler_called_);
+
+    // Verify that we overrode response came
+    EXPECT_EQ("{ \"result\": 777, \"text\": \"replaced response text\" }",
+              answer->str());
+
+    // Make sure invoked the command_processed callout
+    EXPECT_EQ("command_processed_handler", callout_name_);
+
+    // Verify the callout could extract all the context arguments
+    EXPECT_EQ("change-response:[ \"just\", \"some\", \"data\" ]:"
+             "{ \"result\": 2, \"text\": \"'change-response' command not supported.\" }",
+              processed_log_);
+}