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:
@@ -4,4 +4,4 @@ if HAVE_MYSQL
|
||||
SUBDIRS += mysql_cb
|
||||
endif
|
||||
|
||||
SUBDIRS += stat_cmds user_chk
|
||||
SUBDIRS += run_script stat_cmds user_chk
|
||||
|
2
src/hooks/dhcp/run_script/.gitattributes
vendored
Normal file
2
src/hooks/dhcp/run_script/.gitattributes
vendored
Normal 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
1
src/hooks/dhcp/run_script/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/html
|
74
src/hooks/dhcp/run_script/Makefile.am
Normal file
74
src/hooks/dhcp/run_script/Makefile.am
Normal 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
|
||||
|
65
src/hooks/dhcp/run_script/run_script.dox
Normal file
65
src/hooks/dhcp/run_script/run_script.dox
Normal 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.
|
||||
|
||||
*/
|
65
src/hooks/dhcp/run_script/run_script_callouts.cc
Normal file
65
src/hooks/dhcp/run_script/run_script_callouts.cc
Normal 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"
|
||||
|
17
src/hooks/dhcp/run_script/run_script_log.cc
Normal file
17
src/hooks/dhcp/run_script/run_script_log.cc
Normal 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
|
22
src/hooks/dhcp/run_script/run_script_log.h
Normal file
22
src/hooks/dhcp/run_script/run_script_log.h
Normal 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
|
11
src/hooks/dhcp/run_script/run_script_messages.mes
Normal file
11
src/hooks/dhcp/run_script/run_script_messages.mes
Normal 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.
|
17
src/hooks/dhcp/run_script/run_script_wrapper.sh
Executable file
17
src/hooks/dhcp/run_script/run_script_wrapper.sh
Executable 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
|
7
src/hooks/dhcp/run_script/tests/run_script_test.sh
Executable file
7
src/hooks/dhcp/run_script/tests/run_script_test.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
echo ${@}
|
||||
|
||||
SKIP="false"
|
||||
if test ! -z ${ADDRESS}; then
|
||||
echo "${ADDRESS}"
|
||||
SKIP="false"
|
||||
fi
|
@@ -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
|
||||
|
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -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
|
||||
;;
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user