2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 13:37:55 +00:00

[#899] create run_script hook library

* added new library to build sequence
 * added support for environment variables in ProcessSpawn
 * added unittests for environment variables in ProcessSpawn
This commit is contained in:
Razvan Becheriu
2021-01-28 16:22:48 +02:00
parent 5e6edf517b
commit efff46a10b
15 changed files with 354 additions and 20 deletions

View File

@@ -4,4 +4,4 @@ if HAVE_MYSQL
SUBDIRS += mysql_cb
endif
SUBDIRS += stat_cmds user_chk
SUBDIRS += run_script stat_cmds user_chk

View File

@@ -0,0 +1,2 @@
/run_script_messages.cc -diff merge=ours
/run_script_messages.h -diff merge=ours

1
src/hooks/dhcp/run_script/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/html

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,7 @@
echo ${@}
SKIP="false"
if test ! -z ${ADDRESS}; then
echo "${ADDRESS}"
SKIP="false"
fi

View 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

View 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_;
};
}

View 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
;;

View 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