Skip to content

Commit

Permalink
Implement remote tap support (#362)
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonardo Parente authored Aug 11, 2022
1 parent c213e6b commit cc2b1c5
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 7 deletions.
65 changes: 64 additions & 1 deletion src/CoreServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,70 @@ void CoreServer::_setup_routes(const PrometheusConfig &prom_config)
res.set_content(j.dump(), "text/json");
}
});
_svr.Get(fmt::format("/api/v1/taps/({})", AbstractModule::MODULE_ID_REGEX).c_str(), [&](const httplib::Request &req, httplib::Response &res) {
json j = json::object();
auto name = req.matches[1];
if (!_registry->tap_manager()->module_exists(name)) {
res.status = 404;
j["error"] = "tap does not exists";
res.set_content(j.dump(), "text/json");
return;
}
try {
auto [tap, lock] = _registry->tap_manager()->module_get_locked(name);
tap->info_json(j[name]);
res.set_content(j.dump(), "text/json");
} catch (const std::exception &e) {
res.status = 500;
j["error"] = e.what();
res.set_content(j.dump(), "text/json");
}
});
_svr.Post(R"(/api/v1/taps)", [&]([[maybe_unused]] const httplib::Request &req, httplib::Response &res) {
json j = json::object();
if (!req.has_header("Content-Type")) {
res.status = 400;
j["error"] = "must include Content-Type header";
res.set_content(j.dump(), "text/json");
return;
}
auto content_type = req.get_header_value("Content-Type");
if (content_type != "application/x-yaml" && content_type != "application/json") {
res.status = 400;
j["error"] = "Content-Type not supported";
res.set_content(j.dump(), "text/json");
return;
}
try {
auto taps = _registry->tap_manager()->load_from_str(req.body);
for (auto &mod : taps) {
mod->info_json(j[mod->name()]);
}
res.set_content(j.dump(), "text/json");
} catch (const std::exception &e) {
res.status = 500;
j["error"] = e.what();
res.set_content(j.dump(), "text/json");
}
});
_svr.Delete(fmt::format("/api/v1/taps/({})", AbstractModule::MODULE_ID_REGEX).c_str(), [&](const httplib::Request &req, httplib::Response &res) {
json j = json::object();
auto name = req.matches[1];
if (!_registry->tap_manager()->module_exists(name)) {
res.status = 404;
j["error"] = "tap does not exists";
res.set_content(j.dump(), "text/json");
return;
}
try {
_registry->tap_manager()->remove_tap(name);
res.set_content(j.dump(), "text/json");
} catch (const std::exception &e) {
res.status = 500;
j["error"] = e.what();
res.set_content(j.dump(), "text/json");
}
});
// Policies
_svr.Get(R"(/api/v1/policies)", [&]([[maybe_unused]] const httplib::Request &req, httplib::Response &res) {
json j = json::object();
Expand Down Expand Up @@ -375,5 +439,4 @@ void CoreServer::_setup_routes(const PrometheusConfig &prom_config)
}
});
}

}
38 changes: 36 additions & 2 deletions src/Taps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,34 @@

namespace visor {

std::vector<Tap *> TapManager::load_from_str(const std::string &str)
{
if (str.empty()) {
throw TapException("empty data");
}

YAML::Node node = YAML::Load(str);

if (!node.IsMap() || !node["visor"]) {
throw TapException("invalid schema");
}
if (!node["version"] || !node["version"].IsScalar() || node["version"].as<std::string>() != "1.0") {
throw TapException("missing or unsupported version");
}
if (node["visor"]["taps"] && node["visor"]["taps"].IsMap()) {
return load(node["visor"]["taps"], true);
} else {
throw TapException("no taps found in schema");
}
}

// needs to be thread safe and transactional: any errors mean resources get cleaned up with no side effects
void TapManager::load(const YAML::Node &tap_yaml, bool strict)
std::vector<Tap *> TapManager::load(const YAML::Node &tap_yaml, bool strict)
{
assert(tap_yaml.IsMap());
assert(spdlog::get("visor"));

std::vector<Tap *> result;
for (YAML::const_iterator it = tap_yaml.begin(); it != tap_yaml.end(); ++it) {
if (!it->first.IsScalar()) {
throw TapException("expecting tap identifier");
Expand Down Expand Up @@ -60,9 +82,21 @@ void TapManager::load(const YAML::Node &tap_yaml, bool strict)

// will throw if it already exists. nothing else to clean up
module_add(std::move(tap_module));

result.push_back(tap_module.get());
spdlog::get("visor")->info("tap [{}]: loaded, type {}", tap_name, input_type);
}

return result;
}

void TapManager::remove_tap(const std::string &name)
{
std::unique_lock lock(_map_mutex);
if (_map.count(name) == 0) {
throw ModuleException(name, fmt::format("module name '{}' does not exist", name));
}
//TODO: add logic to remove policies that uses the specific deleted TAP
_map.erase(name);
}

std::string Tap::get_input_name(const Configurable &config, const Configurable &filter)
Expand Down
4 changes: 3 additions & 1 deletion src/Taps.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ class TapManager : public AbstractManager<Tap>
{
}

void load(const YAML::Node &tap_yaml, bool strict);
std::vector<Tap *> load_from_str(const std::string &str);
std::vector<Tap *> load(const YAML::Node &tap_yaml, bool strict);
void remove_tap(const std::string &name);
};

}
23 changes: 20 additions & 3 deletions src/tests/test_policies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,24 @@ TEST_CASE("Policies", "[policies]")
REQUIRE_THROWS_WITH(registry.policy_manager()->load(config_file["visor"]["policies"]), "expecting policy configuration map");
}

SECTION("Bad Config: empty data")
{
CoreRegistry registry;
REQUIRE_THROWS_WITH(registry.policy_manager()->load_from_str(""), "empty data");
}

SECTION("Bad Config: invalid schema")
{
CoreRegistry registry;
REQUIRE_THROWS_WITH(registry.policy_manager()->load_from_str("invalid: schema"), "invalid schema");
}

SECTION("Bad Config: missing version")
{
CoreRegistry registry;
REQUIRE_THROWS_WITH(registry.policy_manager()->load_from_str(policies_config_bad1), "missing or unsupported version");
}

SECTION("Bad Config: invalid tap")
{
CoreRegistry registry;
Expand All @@ -772,10 +790,9 @@ TEST_CASE("Policies", "[policies]")
{
CoreRegistry registry;
registry.start(nullptr);
YAML::Node config_file = YAML::Load(policies_config_bad4);

REQUIRE_NOTHROW(registry.tap_manager()->load(config_file["visor"]["taps"], true));
REQUIRE_THROWS_WITH(registry.policy_manager()->load(config_file["visor"]["policies"]), "policy [default_view] failed to start: mock error on start");
REQUIRE_NOTHROW(registry.tap_manager()->load_from_str(policies_config_bad4));
REQUIRE_THROWS_WITH(registry.policy_manager()->load_from_str(policies_config_bad4), "policy [default_view] failed to start: mock error on start");
}

SECTION("Bad Config: mis-matched input_type on tap")
Expand Down
76 changes: 76 additions & 0 deletions src/tests/test_taps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ version: "1.0"
iface: en7
)";

auto tap_config_bad_version = R"(
version: "2.0"
visor:
taps:
wired:
input_type: nonexistent
config:
iface: en7
)";

auto tap_config_bad_no_tap = R"(
version: "1.0"
visor:
policies:
default:
input_type: nonexistent
)";

TEST_CASE("Taps", "[taps]")
{

Expand All @@ -65,6 +85,34 @@ TEST_CASE("Taps", "[taps]")
CHECK(tap->config_get<bool>("boolean") == true);
}

SECTION("Good config, test remove tap and add again")
{
CoreRegistry registry;
registry.start(nullptr);
YAML::Node config_file = YAML::Load(tap_config);

CHECK(config_file["visor"]["taps"]);
CHECK(config_file["visor"]["taps"].IsMap());
CHECK_NOTHROW(registry.tap_manager()->load(config_file["visor"]["taps"], true));

auto [tap, lock] = registry.tap_manager()->module_get_locked("wired");
CHECK(tap->name() == "wired");
CHECK(tap->config_get<std::string>("iface") == "en7");
CHECK(tap->config_get<uint64_t>("number") == 123);
CHECK(tap->config_get<bool>("boolean") == true);
lock.unlock();

REQUIRE_NOTHROW(registry.tap_manager()->remove_tap("wired"));
REQUIRE_NOTHROW(registry.tap_manager()->remove_tap("wireless"));

CHECK_NOTHROW(registry.tap_manager()->load(config_file["visor"]["taps"], true));
auto [new_tap, new_lock] = registry.tap_manager()->module_get_locked("wired");
CHECK(new_tap->name() == "wired");
CHECK(new_tap->config_get<std::string>("iface") == "en7");
CHECK(new_tap->config_get<uint64_t>("number") == 123);
CHECK(new_tap->config_get<bool>("boolean") == true);
}

SECTION("Duplicate")
{
CoreRegistry registry;
Expand All @@ -86,6 +134,34 @@ TEST_CASE("Taps", "[taps]")
CHECK_THROWS(registry.tap_manager()->load(config_file["visor"]["taps"], true));
}

SECTION("Bad Config: empty data")
{
CoreRegistry registry;
registry.start(nullptr);
REQUIRE_THROWS_WITH(registry.tap_manager()->load_from_str(""), "empty data");
}

SECTION("Bad Config: invalid schema")
{
CoreRegistry registry;
registry.start(nullptr);
REQUIRE_THROWS_WITH(registry.tap_manager()->load_from_str("invalid: schema"), "invalid schema");
}

SECTION("Bad Config: invalid version")
{
CoreRegistry registry;
registry.start(nullptr);
REQUIRE_THROWS_WITH(registry.tap_manager()->load_from_str(tap_config_bad_version), "missing or unsupported version");
}

SECTION("Bad Config: no taps")
{
CoreRegistry registry;
registry.start(nullptr);
REQUIRE_THROWS_WITH(registry.tap_manager()->load_from_str(tap_config_bad_no_tap), "no taps found in schema");
}

SECTION("Json validation")
{
CoreRegistry registry;
Expand Down

0 comments on commit cc2b1c5

Please sign in to comment.