]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#899] create run_script hook library
authorRazvan Becheriu <razvan@isc.org>
Thu, 28 Jan 2021 14:22:48 +0000 (16:22 +0200)
committerRazvan Becheriu <razvan@isc.org>
Thu, 18 Feb 2021 17:14:26 +0000 (19:14 +0200)
 * added new library to build sequence
 * added support for environment variables in ProcessSpawn
 * added unittests for environment variables in ProcessSpawn

15 files changed:
src/hooks/dhcp/Makefile.am
src/hooks/dhcp/run_script/.gitattributes [new file with mode: 0644]
src/hooks/dhcp/run_script/.gitignore [new file with mode: 0644]
src/hooks/dhcp/run_script/Makefile.am [new file with mode: 0644]
src/hooks/dhcp/run_script/run_script.dox [new file with mode: 0644]
src/hooks/dhcp/run_script/run_script_callouts.cc [new file with mode: 0644]
src/hooks/dhcp/run_script/run_script_log.cc [new file with mode: 0644]
src/hooks/dhcp/run_script/run_script_log.h [new file with mode: 0644]
src/hooks/dhcp/run_script/run_script_messages.mes [new file with mode: 0644]
src/hooks/dhcp/run_script/run_script_wrapper.sh [new file with mode: 0755]
src/hooks/dhcp/run_script/tests/run_script_test.sh [new file with mode: 0755]
src/lib/util/process_spawn.cc
src/lib/util/process_spawn.h
src/lib/util/tests/process_spawn_app.sh.in
src/lib/util/tests/process_spawn_unittest.cc

index 090bde7fd3659db784d8db55362e861ea4247cea..b40e08c3fd059e2334b246d02c77c64c86bcf833 100644 (file)
@@ -4,4 +4,4 @@ if HAVE_MYSQL
 SUBDIRS += mysql_cb
 endif
 
-SUBDIRS += stat_cmds user_chk
+SUBDIRS += run_script stat_cmds user_chk
diff --git a/src/hooks/dhcp/run_script/.gitattributes b/src/hooks/dhcp/run_script/.gitattributes
new file mode 100644 (file)
index 0000000..31e5a07
--- /dev/null
@@ -0,0 +1,2 @@
+/run_script_messages.cc               -diff merge=ours
+/run_script_messages.h                -diff merge=ours
diff --git a/src/hooks/dhcp/run_script/.gitignore b/src/hooks/dhcp/run_script/.gitignore
new file mode 100644 (file)
index 0000000..35b5e99
--- /dev/null
@@ -0,0 +1 @@
+/html
diff --git a/src/hooks/dhcp/run_script/Makefile.am b/src/hooks/dhcp/run_script/Makefile.am
new file mode 100644 (file)
index 0000000..0fa924c
--- /dev/null
@@ -0,0 +1,74 @@
+SUBDIRS = . libloadtests tests
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS  = $(KEA_CXXFLAGS)
+
+# Ensure that the message file and doxygen file is included in the distribution
+EXTRA_DIST = run_script_messages.mes
+EXTRA_DIST += run_script.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# convenience archive
+
+noinst_LTLIBRARIES = librun_script.la
+
+librun_script_la_SOURCES  = run_script_callouts.cc
+librun_script_la_SOURCES += run_script_log.cc run_script_log.h
+librun_script_la_SOURCES += run_script_messages.cc run_script_messages.h
+librun_script_la_SOURCES += version.cc
+
+librun_script_la_CXXFLAGS = $(AM_CXXFLAGS)
+librun_script_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+# install the shared object into $(libdir)/kea/hooks
+lib_hooksdir = $(libdir)/kea/hooks
+lib_hooks_LTLIBRARIES = libdhcp_run_script.la
+
+libdhcp_run_script_la_SOURCES  =
+libdhcp_run_script_la_LDFLAGS  = $(AM_LDFLAGS)
+libdhcp_run_script_la_LDFLAGS  += -avoid-version -export-dynamic -module
+libdhcp_run_script_la_LIBADD = librun_script.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_run_script_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp_run_script_la_LIBADD += $(LOG4CPLUS_LIBS)
+libdhcp_run_script_la_LIBADD += $(CRYPTO_LIBS)
+libdhcp_run_script_la_LIBADD += $(BOOST_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required.  To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+       rm -f run_script_messages.h run_script_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: run_script_messages.h run_script_messages.cc
+       @echo Message files regenerated
+
+run_script_messages.h run_script_messages.cc: run_script_messages.mes
+       $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/hooks/dhcp/run_script/run_script_messages.mes
+
+else
+
+messages run_script_messages.h run_script_messages.cc:
+       @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
diff --git a/src/hooks/dhcp/run_script/run_script.dox b/src/hooks/dhcp/run_script/run_script.dox
new file mode 100644 (file)
index 0000000..45ecf76
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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/.
+
+/**
+
+@page libdhcp_run_script Kea Run Script Hooks Library
+
+@section libdhcp_run_scriptIntro Introduction
+
+Welcome to Kea Run Script Hooks Library. This documentation is addressed to
+developers who are interested in the internal operation of the Run Script
+library. This file provides information needed to understand and
+perhaps extend this library.
+
+This documentation is stand-alone: you should have read and understood
+the <a href="https://jenkins.isc.org/job/Kea_doc/doxygen/">Kea
+Developer's Guide</a> and in particular its section about hooks.
+
+@section libdhcp_run_scriptUser Now To Use libdhcp_run_script
+## Introduction
+libdhcp_run_script is a hooks library which allows an external script to
+be run on specific hook points.
+
+## Configuring the DHCPv4 Module
+
+It must be configured as a hook library for the desired DHCP server
+modules. Note that the bootp library is installed alongside the
+Kea libraries in "<install-dir>/lib" where <install-dir> is determined
+by the --prefix option of the configure script.  It defaults to
+"/usr/local". Assuming the default value then, configuring kea-dhcp4
+to load the bootp library could be done with the following Kea4
+configuration:
+
+@code
+"Dhcp4": {
+    "hook_libraries": [
+        {   "library": "/usr/local/lib/libdhcp_run_script.so",
+            "parameters": { "name": "/path_to/script_name.sh",
+            "sync": false }},
+        ...
+    ]
+}
+@endcode
+
+The parameters contain the 'name' which indicates the path and name of the
+external script to be called on each hookpoint, and also the 'sync' option
+to be able to wait synchronously for the script to finish execution.
+If the 'sync' parameter is false, then the script will be launched and all
+the OUT parameters of the script will be ignored.
+
+## Internal operation
+
+The first function called in @ref load() located in the
+bootp_callouts.cc. It checks if the necessary parameter is passed and
+decodes the option configurations. @ref unload() free the configuration.
+
+Kea engine checks if the library has functions that match known hook
+point names. This library has several such functions: @ref pkt4_receive,
+@ref pkt4_send, @ref pkt6_receive, @ref pkt6_send, etc...
+located in run_script_callouts.cc.
+
+*/
diff --git a/src/hooks/dhcp/run_script/run_script_callouts.cc b/src/hooks/dhcp/run_script/run_script_callouts.cc
new file mode 100644 (file)
index 0000000..5478509
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright (C) 2021 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 <cc/command_interpreter.h>
+#include <hooks/hooks.h>
+
+namespace isc {
+namespace run_script {
+
+RunScriptPtr impl;
+
+} // end of namespace flex_option
+} // end of namespace isc
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace isc::run_script;
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+/// @brief This function is called when the library is loaded.
+///
+/// @param handle library handle
+/// @return 0 when initialization is successful, 1 otherwise
+int load(LibraryHandle& handle) {
+    try {
+        impl.reset(new RunScriptImpl());
+    } catch (const std::exception& ex) {
+        LOG_ERROR(run_script_logger, RUN_SCRIPT_LOAD_ERROR)
+            .arg(ex.what());
+        return (1);
+    }
+
+    LOG_INFO(run_script_logger, RUN_SCRIPT_LOAD);
+    return (0);
+}
+
+/// @brief This function is called when the library is unloaded.
+///
+/// @return always 0.
+int unload() {
+    impl.reset();
+    LOG_INFO(run_script_logger, RUN_SCRIPT_UNLOAD);
+    return (0);
+}
+
+/// @brief This function is called to retrieve the multi-threading compatibility.
+///
+/// @return 1 which means compatible with multi-threading.
+int multi_threading_compatible() {
+    return (1);
+}
+
+} // end extern "C"
+
diff --git a/src/hooks/dhcp/run_script/run_script_log.cc b/src/hooks/dhcp/run_script/run_script_log.cc
new file mode 100644 (file)
index 0000000..62af75b
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 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 <run_script_log.h>
+
+namespace isc {
+namespace run_script {
+
+isc::log::Logger run_script_logger("run-script-hooks");
+
+} // namespace bootp
+} // namespace isc
diff --git a/src/hooks/dhcp/run_script/run_script_log.h b/src/hooks/dhcp/run_script/run_script_log.h
new file mode 100644 (file)
index 0000000..10a23d9
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 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 RUN_SCRIPT_LOG_H
+#define RUN_SCRIPT_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <log/log_dbglevels.h>
+#include <run_script_messages.h>
+
+namespace isc {
+namespace run_script {
+
+extern isc::log::Logger run_script_logger;
+
+} // end of namespace run_script
+} // end of namespace isc
+#endif
diff --git a/src/hooks/dhcp/run_script/run_script_messages.mes b/src/hooks/dhcp/run_script/run_script_messages.mes
new file mode 100644 (file)
index 0000000..348b4ae
--- /dev/null
@@ -0,0 +1,11 @@
+# Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC")
+
+% RUN_SCRIPT_LOAD Run Script hooks library has been loaded
+This info message indicates that the Run Script hooks library has been loaded.
+
+% RUN_SCRIPT_LOAD_ERROR Run Script hooks library failed: %1
+This error message indicates an error during loading the Run Script hooks
+library. The details of the error are provided as argument of the log message.
+
+% RUN_SCRIPT_UNLOAD Run Script hooks library has been unloaded
+This info message indicates that the RunScript hooks library has been unloaded.
diff --git a/src/hooks/dhcp/run_script/run_script_wrapper.sh b/src/hooks/dhcp/run_script/run_script_wrapper.sh
new file mode 100755 (executable)
index 0000000..5687af8
--- /dev/null
@@ -0,0 +1,17 @@
+if test ${#} -lt 2; then
+       echo "Usage: ${0} script_name function_name"
+       echo "     All variables used by the script must be available"
+       echo "     as environment variables."
+       echo "     All variables specified in the 'hook_parameters'"
+       echo "     variable will be printed to stdout."
+       exit 1
+fi
+
+script_name=${1}
+function_name=${2}
+
+source ${script_name} ${function_name}
+
+for parameter in ${hook_parameters}; do
+       echo "${parameter}=${!parameter}"
+done
diff --git a/src/hooks/dhcp/run_script/tests/run_script_test.sh b/src/hooks/dhcp/run_script/tests/run_script_test.sh
new file mode 100755 (executable)
index 0000000..eac4366
--- /dev/null
@@ -0,0 +1,7 @@
+echo ${@}
+
+SKIP="false"
+if test ! -z ${ADDRESS}; then
+       echo "${ADDRESS}"
+       SKIP="false"
+fi
index 1cc371735698910fb78c06b9255ad64b7e3d9eaa..249bc968629b7d96580b9f147094b16b6ca2ed07 100644 (file)
@@ -59,8 +59,10 @@ public:
     ///
     /// @param executable A path to the program to be executed.
     /// @param args Arguments for the program to be executed.
+    /// @param vars Environment variables for the program to be executed.
     ProcessSpawnImpl(const std::string& executable,
-                     const ProcessArgs& args);
+                     const ProcessArgs& args,
+                     const ProcessEnvVars& vars);
 
     /// @brief Destructor.
     ~ProcessSpawnImpl();
@@ -146,13 +148,18 @@ private:
     std::string executable_;
 
     /// @brief An array holding arguments for the executable.
-    char** args_;
+    boost::shared_ptr<char*[]> args_;
+
+    /// @brief An array holding environment variables for the executable.
+    boost::shared_ptr<char*[]> vars_;
 };
 
 ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
-                                   const ProcessArgs& args)
+                                   const ProcessArgs& args,
+                                   const ProcessEnvVars& vars)
     : signals_(new SignalSet(SIGCHLD)), process_state_(),
-      executable_(executable), args_(new char*[args.size() + 2]) {
+      executable_(executable), args_(new char*[args.size() + 2]),
+      vars_(new char*[vars.size() + 1]) {
     // Set the handler which is invoked immediately when the signal
     // is received.
     signals_->setOnReceiptHandler(std::bind(&ProcessSpawnImpl::waitForProcess,
@@ -160,12 +167,17 @@ ProcessSpawnImpl::ProcessSpawnImpl(const std::string& executable,
     // Conversion of the arguments to the C-style array we start by setting
     // all pointers within an array to NULL to indicate that they haven't
     // been allocated yet.
-    memset(args_, 0, (args.size() + 2) * sizeof(char*));
+    memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
+    memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
     // By convention, the first argument points to an executable name.
     args_[0] = allocateArg(executable_);
     // Copy arguments to the array.
     for (int i = 1; i <= args.size(); ++i) {
-        args_[i] = allocateArg(args[i-1]);
+        args_[i] = allocateArg(args[i - 1]);
+    }
+    // Copy environment variables to the array.
+    for (int i = 0; i < vars.size(); ++i) {
+        vars_[i] = allocateArg(vars[i]);
     }
 }
 
@@ -176,8 +188,13 @@ ProcessSpawnImpl::~ProcessSpawnImpl() {
         delete[] args_[i];
         ++i;
     }
-    // Deallocate the array.
-    delete[] args_;
+
+    i = 0;
+    // Deallocate strings in the array of environment variables.
+    while (vars_[i] != NULL) {
+        delete[] vars_[i];
+        ++i;
+    }
 }
 
 std::string
@@ -217,8 +234,8 @@ ProcessSpawnImpl::spawn() {
         // We're in the child process.
         sigprocmask(SIG_SETMASK, &osset, 0);
         // Run the executable.
-        if (execvp(executable_.c_str(), args_) != 0) {
-            // We may end up here if the execvp failed, e.g. as a result
+        if (execvpe(executable_.c_str(), args_.get(), vars_.get()) != 0) {
+            // We may end up here if the execvpe failed, e.g. as a result
             // of issue with permissions or invalid executable name.
             _exit(EXIT_FAILURE);
         }
@@ -332,12 +349,12 @@ ProcessSpawnImpl::clearState(const pid_t pid) {
 }
 
 ProcessSpawn::ProcessSpawn(const std::string& executable,
-                           const ProcessArgs& args)
-    : impl_(new ProcessSpawnImpl(executable, args)) {
+                           const ProcessArgs& args,
+                           const ProcessEnvVars& vars)
+    : impl_(new ProcessSpawnImpl(executable, args, vars)) {
 }
 
 ProcessSpawn::~ProcessSpawn() {
-    delete impl_;
 }
 
 std::string
index dffd365e9bc6d92f039cada988b1ce7f08fd03f1..c4e931047da4979fc27e251963c58564b6d594b6 100644 (file)
@@ -12,6 +12,7 @@
 #include <string>
 #include <sys/types.h>
 #include <vector>
+#include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace util {
@@ -27,10 +28,17 @@ public:
 /// class.
 class ProcessSpawnImpl;
 
+/// @brief Pointer to a ProcessSpawnImpl class.
+typedef boost::shared_ptr<ProcessSpawnImpl> ProcessSpawnImplPtr;
+
 /// @brief Type of the container holding arguments of the executable
 /// being run as a background process.
 typedef std::vector<std::string> ProcessArgs;
 
+/// @brief Type of the container holding environment variables of the executable
+/// being run as a background process.
+typedef std::vector<std::string> ProcessEnvVars;
+
 /// @brief Utility class for spawning new processes.
 ///
 /// This class is used to spawn new process by Kea. It forks the current
@@ -64,8 +72,10 @@ public:
     ///
     /// @param executable A path to the program to be executed.
     /// @param args Arguments for the program to be executed.
+    /// @param vars Environment variables for the program to be executed.
     ProcessSpawn(const std::string& executable,
-                 const ProcessArgs& args = ProcessArgs());
+                 const ProcessArgs& args = ProcessArgs(),
+                 const ProcessEnvVars& = ProcessEnvVars());
 
     /// @brief Destructor.
     ~ProcessSpawn();
@@ -132,9 +142,8 @@ public:
 
 private:
 
-    /// @brief A pointer to the implementation of this class.
-    ProcessSpawnImpl* impl_;
-
+    /// @brief A smart pointer to the implementation of this class.
+    ProcessSpawnImplPtr impl_;
 };
 
 }
index c0b4a54b4c83549d93f3ebafd76603d3a2bfb36c..806e7bd702fd0137e1cf5ff855a8a866b98e17a1 100644 (file)
@@ -45,6 +45,16 @@ do
             shift
             sleep "${1}"
             ;;
+        -v)
+            shift
+            VAR_NAME=${1}
+            shift
+            VAR_VALUE=${1}
+            EXPECTED=$(env | grep ${VAR_NAME})
+            if ! test "${VAR_NAME}=${VAR_VALUE}" = "${EXPECTED}"; then
+                exit 123
+            fi
+            ;;
         *)
             exit 123
             ;;
index 016fbbebcbea4765c8019fec262035014b63ae55..6137af9d7f4d6fd90b562bb2d28723ec090d1f6a 100644 (file)
@@ -122,6 +122,25 @@ TEST(ProcessSpawn, spawnWithArgs) {
     EXPECT_EQ(64, process.getExitStatus(pid));
 }
 
+// This test verifies that the external application can be ran with
+// arguments and environment variables that the exit code is gathered.
+TEST(ProcessSpawn, spawnWithArgsAndEnvVars) {
+    std::vector<std::string> args;
+    std::vector<std::string> vars;
+    args.push_back("-v");
+    args.push_back("TEST_VARIABLE_NAME");
+    args.push_back("TEST_VARIABLE_VALUE");
+    vars.push_back("TEST_VARIABLE_NAME=TEST_VARIABLE_VALUE");
+
+    ProcessSpawn process(getApp(), args, vars);
+    pid_t pid = 0;
+    ASSERT_NO_THROW(pid = process.spawn());
+
+    ASSERT_TRUE(waitForProcess(process, pid, 2));
+
+    EXPECT_EQ(32, process.getExitStatus(pid));
+}
+
 // This test verifies that the single ProcessSpawn object can be used
 // to start two processes and that their status codes can be gathered.
 // It also checks that it is possible to clear the status of the
@@ -164,7 +183,6 @@ TEST(ProcessSpawn, spawnNoArgs) {
     EXPECT_EQ(32, process.getExitStatus(pid));
 }
 
-
 // This test verifies that the EXIT_FAILURE code is returned when
 // application can't be executed.
 TEST(ProcessSpawn, invalidExecutable) {
@@ -247,5 +265,4 @@ TEST(ProcessSpawn, errnoInvariance) {
     EXPECT_EQ(123, errno);
 }
 
-
 } // end of anonymous namespace