Skip to content

Commit

Permalink
Support --load-modules option for simple_switch_grpc
Browse files Browse the repository at this point in the history
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
  • Loading branch information
antoninbas committed Feb 21, 2019
1 parent 0557502 commit d434083
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 74 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions include/bm/bm_sim/target_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,30 @@ class TargetParserBasic : public TargetParserIface {
std::unique_ptr<TargetParserBasicStore> 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<std::string> &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_
64 changes: 61 additions & 3 deletions src/bm_sim/target_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,23 @@
*
*/

#include <bm/config.h>

#ifdef BM_HAVE_DLOPEN
#include <dlfcn.h>
#endif // BM_HAVE_DLOPEN

#include <bm/bm_sim/_assert.h>
#include <bm/bm_sim/target_parser.h>

#include <boost/program_options.hpp>

#include <string>
#include <vector>
#include <memory>
#include <unordered_set>
#include <ostream>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>

namespace po = boost::program_options;

Expand Down Expand Up @@ -173,4 +181,54 @@ TargetParserBasic::get_flag_option(const std::string &name, bool *v) const {
return var_store->get_option<bool>(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<std::string> &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
80 changes: 11 additions & 69 deletions targets/simple_switch/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,81 +22,14 @@

#include <bm/config.h>

#ifdef BM_HAVE_DLOPEN
# include <dlfcn.h>
#endif // BM_HAVE_DLOPEN
#include <bm/SimpleSwitch.h>
#include <bm/bm_runtime/bm_runtime.h>
#include <bm/bm_sim/target_parser.h>

#include <sstream>
#include <string>
#include <vector>

#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<std::string> &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 {
Expand All @@ -106,11 +39,20 @@ shared_ptr<SimpleSwitchIf> 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;
Expand Down
3 changes: 2 additions & 1 deletion targets/simple_switch_grpc/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion targets/simple_switch_grpc/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit d434083

Please sign in to comment.