From d4340832121be1be3852ca0bef709f6443ef86ed Mon Sep 17 00:00:00 2001 From: Antonin Bas Date: Wed, 20 Feb 2019 17:19:37 -0800 Subject: [PATCH] Support --load-modules option for simple_switch_grpc Since this is useful for many targets (simple_switch, simple_switch_grpc and possibly psa_switch) and has been around for a while, I decided to move support for --load-modules to the core bmv2 library, by adding a new reference target-specific option parser (`TargetParserBasicWithDynModules`) that targets can use directly. The reason why this functionality did not find its way into the main bmv2 option parser is because it requires the target to be linked with -rdynamic, so I believe it is better for the target to choose whether this functionality is required or not. Not moving it to the main option parser also guarantees backward-compatibility for simple_switch users. Fixes #719 --- README.md | 12 ++++ include/bm/bm_sim/target_parser.h | 24 ++++++++ src/bm_sim/target_parser.cpp | 64 ++++++++++++++++++++- targets/simple_switch/main.cpp | 80 ++++---------------------- targets/simple_switch_grpc/Makefile.am | 3 +- targets/simple_switch_grpc/main.cpp | 2 +- 6 files changed, 111 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 041a85583..972b99f9f 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,18 @@ switch is running: The script will display events of significance (table hits / misses, parser transitions, ...) for each packet. +## Loading shared objects dynamically + +Some targets (simple_switch and simple_switch_grpc) let the user load shared +libraries dynamically at runtime. This is done by using the target-specific +command-line option `--load-modules`, which takes as a parameter a +comma-separated list of shared objects. This functionality is currently only +available on systems where `dlopen` is available. Make sure that the shared +objects are visible by the dynamic loader (e.g. by setting `LD_LIBRARY_PATH` +appropriately on Linux). You can control whether this feature is available by +using `--enable-modules` / `--disable-modules` when configuring bmv2. By +default, this feature is enabled when `dlopen` is available. + ## Integrating with Mininet We will provide more information in a separate document. However you can test diff --git a/include/bm/bm_sim/target_parser.h b/include/bm/bm_sim/target_parser.h index f9a998fa2..67c78280e 100644 --- a/include/bm/bm_sim/target_parser.h +++ b/include/bm/bm_sim/target_parser.h @@ -124,6 +124,30 @@ class TargetParserBasic : public TargetParserIface { std::unique_ptr var_store; }; +//! Same as TargetParserBasic but supports the '--load-modules' command-line +//! option by default. This option is used to load shared objects dynamically at +//! runtime. Often, these objects / modules contain new primtive action or +//! extern definitions. If you plan on using '--load-modules' in your target, +//! and you plan on providing primitive / extern definitions dynamically, you +//! will need to link your executable with -rdynamic. +class TargetParserBasicWithDynModules : public TargetParserBasic { + public: + TargetParserBasicWithDynModules(); + ~TargetParserBasicWithDynModules(); + + //! See bm::TargetParserIface::parse. Make sure all possible options have been + //! registered using add_string_option(), add_int_option(), add_uint_option() + //! or add_flag_option(). + int parse(const std::vector &more_options, + std::ostream *errstream) override; + + private: + //! Name of the option. + static constexpr char load_modules_option[] = "load-modules"; + + int load_modules(std::ostream *errstream); +}; + } // namespace bm #endif // BM_BM_SIM_TARGET_PARSER_H_ diff --git a/src/bm_sim/target_parser.cpp b/src/bm_sim/target_parser.cpp index 4416d5a24..5e905d88c 100644 --- a/src/bm_sim/target_parser.cpp +++ b/src/bm_sim/target_parser.cpp @@ -18,15 +18,23 @@ * */ +#include + +#ifdef BM_HAVE_DLOPEN +#include +#endif // BM_HAVE_DLOPEN + +#include #include #include -#include -#include #include -#include #include +#include +#include +#include +#include namespace po = boost::program_options; @@ -173,4 +181,54 @@ TargetParserBasic::get_flag_option(const std::string &name, bool *v) const { return var_store->get_option(name, v); } +TargetParserBasicWithDynModules::TargetParserBasicWithDynModules() { +#ifdef BM_ENABLE_MODULES + add_string_option(load_modules_option, + "Load the given .so files (comma-separated) as modules."); +#endif // BM_ENABLE_MODULES +} + +TargetParserBasicWithDynModules::~TargetParserBasicWithDynModules() = default; + +int +TargetParserBasicWithDynModules::parse( + const std::vector &more_options, + std::ostream *errstream) { + int rc = 0; + if ((rc = TargetParserBasic::parse(more_options, errstream)) != 0) return rc; +#ifdef BM_ENABLE_MODULES + if ((rc = load_modules(errstream)) != 0) return rc; +#endif // BM_ENABLE_MODULES + return 0; +} + +int +TargetParserBasicWithDynModules::load_modules(std::ostream *errstream) { + _BM_UNUSED(errstream); +#ifdef BM_ENABLE_MODULES + std::string modules; + ReturnCode retval = get_string_option(load_modules_option, &modules); + if (retval == ReturnCode::OPTION_NOT_PROVIDED) + return 0; + if (retval != ReturnCode::SUCCESS) + return 1; // Unexpected error + std::istringstream iss(modules); + std::string module; + while (std::getline(iss, module, ',')) { +#ifdef BM_HAVE_DLOPEN + if (!dlopen(module.c_str(), RTLD_NOW | RTLD_GLOBAL)) { + *errstream << "WARNING: Skipping module: " << module << ": " + << dlerror() << std::endl; + } +#else // BM_HAVE_DLOPEN +#error modules enabled, but no loading method available +#endif // BM_HAVE_DLOPEN + } +#endif // BM_ENABLE_MODULES + return 0; +} + +/* static */ +constexpr char TargetParserBasicWithDynModules::load_modules_option[]; + } // namespace bm diff --git a/targets/simple_switch/main.cpp b/targets/simple_switch/main.cpp index 93e5a5c24..d19b71950 100644 --- a/targets/simple_switch/main.cpp +++ b/targets/simple_switch/main.cpp @@ -22,81 +22,14 @@ #include -#ifdef BM_HAVE_DLOPEN -# include -#endif // BM_HAVE_DLOPEN #include #include #include -#include -#include -#include - #include "simple_switch.h" namespace { SimpleSwitch *simple_switch; - -std::string load_modules_option = "load-modules"; - -class SimpleSwitchParser : public bm::TargetParserBasic { - public: - SimpleSwitchParser() { - add_flag_option("enable-swap", - "enable JSON swapping at runtime"); -#ifdef BM_ENABLE_MODULES - add_string_option(load_modules_option, - "load the given .so files as modules"); -#endif // BM_ENABLE_MODULES - } - - int parse(const std::vector &more_options, - std::ostream *errstream) override { - int result = ::bm::TargetParserBasic::parse(more_options, errstream); -#ifdef BM_ENABLE_MODULES - load_modules(errstream); -#endif // BM_ENABLE_MODULES - set_enable_swap(); - return result; - } - - protected: -#ifdef BM_ENABLE_MODULES - int load_modules(std::ostream *errstream) { - std::string modules; - ReturnCode retval = get_string_option(load_modules_option, &modules); - if (retval == ReturnCode::OPTION_NOT_PROVIDED) { - return 0; - } - if (retval != ReturnCode::SUCCESS) { - return -1; // Unexpected error - } - std::istringstream iss(modules); - std::string module; - while (std::getline(iss, module, ',')) { -# ifdef BM_HAVE_DLOPEN - if (!dlopen(module.c_str(), RTLD_NOW | RTLD_GLOBAL)) { - *errstream << "WARNING: Skipping module: " << module << ": " - << dlerror() << std::endl; - } -# else // BM_HAVE_DLOPEN - #error modules enabled, but no loading method available -# endif // BM_HAVE_DLOPEN - } - return 0; - } -#endif // BM_ENABLE_MODULES - - void set_enable_swap() { - bool enable_swap = false; - if (get_flag_option("enable-swap", &enable_swap) != ReturnCode::SUCCESS) - std::exit(1); - if (enable_swap) simple_switch->enable_config_swap(); - } -}; - -SimpleSwitchParser *simple_switch_parser; } // namespace namespace sswitch_runtime { @@ -106,11 +39,20 @@ shared_ptr get_handler(SimpleSwitch *sw); int main(int argc, char* argv[]) { simple_switch = new SimpleSwitch(); - simple_switch_parser = new SimpleSwitchParser(); + bm::TargetParserBasicWithDynModules simple_switch_parser; + simple_switch_parser.add_flag_option("enable-swap", + "enable JSON swapping at runtime"); int status = simple_switch->init_from_command_line_options( - argc, argv, simple_switch_parser); + argc, argv, &simple_switch_parser); if (status != 0) std::exit(status); + bool enable_swap_flag = false; + if (simple_switch_parser.get_flag_option("enable-swap", &enable_swap_flag) + != bm::TargetParserBasic::ReturnCode::SUCCESS) { + std::exit(1); + } + if (enable_swap_flag) simple_switch->enable_config_swap(); + int thrift_port = simple_switch->get_runtime_port(); bm_runtime::start_server(simple_switch, thrift_port); using ::sswitch_runtime::SimpleSwitchIf; diff --git a/targets/simple_switch_grpc/Makefile.am b/targets/simple_switch_grpc/Makefile.am index 26c3ba53a..1a8510cc4 100644 --- a/targets/simple_switch_grpc/Makefile.am +++ b/targets/simple_switch_grpc/Makefile.am @@ -27,7 +27,8 @@ libsimple_switch_grpc.la # We follow this tutorial to link with grpc++_reflection: # https://github.com/grpc/grpc/blob/master/doc/server_reflection_tutorial.md simple_switch_grpc_LDFLAGS = \ --Wl,--no-as-needed,-lgrpc++_reflection,--as-needed +-Wl,--no-as-needed,-lgrpc++_reflection,--as-needed \ +-rdynamic lib_LTLIBRARIES = libbm_grpc_dataplane.la noinst_LTLIBRARIES = libsimple_switch_grpc.la diff --git a/targets/simple_switch_grpc/main.cpp b/targets/simple_switch_grpc/main.cpp index 53fe0f44b..b6b3f001f 100644 --- a/targets/simple_switch_grpc/main.cpp +++ b/targets/simple_switch_grpc/main.cpp @@ -27,7 +27,7 @@ int main(int argc, char* argv[]) { - bm::TargetParserBasic simple_switch_parser; + bm::TargetParserBasicWithDynModules simple_switch_parser; simple_switch_parser.add_flag_option( "disable-swap", "disable JSON swapping at runtime");