[PATCH 1/3] httpserver: allow compiling out features not needed for monitoring api

4 views
Skip to first unread message

Waldemar Kozaczuk

unread,
Mar 19, 2020, 4:45:16 PM3/19/20
to osv...@googlegroups.com, Waldemar Kozaczuk
This patch modifies httpserver-api source code to add pre-processor
conditionals - "#if !defined(MONITORING)" - to disable fragments of
code that are not needed for monitoring api. More specifically it disables
all non-GET routes (with exception of trace API) and YAML file-based configuration.
It also disables SSL. All of which is done to allow creating minimal "read-only" version
of httpserver API intended for monitoring purposes only.

Finally it also explicitly makes only certains symbols (like "main") public
by using compiler directives like "__attribute__((visibility("default")))"
and "#pragma GCC visibility push(default)/#pragma GCC visibility pop". The
latter is used to make portion of common code public so that is available to modules
like trace API. Hiding most symbols in monitoring module also helps
to reduce its size.

Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>
---
modules/httpserver-api/api/api.cc | 4 +++
modules/httpserver-api/api/env.cc | 5 +++-
modules/httpserver-api/api/file.cc | 8 ++++++
modules/httpserver-api/api/fs.cc | 2 ++
modules/httpserver-api/api/hardware.cc | 2 ++
modules/httpserver-api/api/network.cc | 3 ++-
modules/httpserver-api/api/os.cc | 8 ++++++
modules/httpserver-api/api/trace.cc | 2 +-
modules/httpserver-api/global_server.cc | 28 ++++++++++++++++++--
modules/httpserver-api/global_server.hh | 4 +++
modules/httpserver-api/handlers.cc | 4 +++
modules/httpserver-api/handlers.hh | 6 ++++-
modules/httpserver-api/json/api_docs.cc | 2 ++
modules/httpserver-api/json/api_docs.hh | 2 ++
modules/httpserver-api/json/formatter.cc | 2 ++
modules/httpserver-api/json/formatter.hh | 2 ++
modules/httpserver-api/json/json_elements.cc | 2 ++
modules/httpserver-api/json/json_elements.hh | 2 ++
modules/httpserver-api/json/json_path.cc | 2 ++
modules/httpserver-api/json/json_path.hh | 2 ++
modules/httpserver-api/main.cc | 2 +-
modules/httpserver-api/path_holder.cc | 2 ++
modules/httpserver-api/path_holder.hh | 3 ++-
modules/httpserver-api/reply.cc | 2 ++
modules/httpserver-api/reply.hh | 2 ++
modules/httpserver-api/server.cc | 10 +++++++
26 files changed, 105 insertions(+), 8 deletions(-)

diff --git a/modules/httpserver-api/api/api.cc b/modules/httpserver-api/api/api.cc
index a1339e67..4206e695 100644
--- a/modules/httpserver-api/api/api.cc
+++ b/modules/httpserver-api/api/api.cc
@@ -240,19 +240,23 @@ private:
routes& _routes;
};

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::api::init(*routes);
}
+#endif

void init(routes& routes)
{
api_json_init_path("Advanced API options");

api_batch.set_handler(new api_param_handler(routes));
+#if !defined(MONITORING)
stop_api.set_handler([](const_req req){
global_server::stop();
return "";
});
+#endif

}

diff --git a/modules/httpserver-api/api/env.cc b/modules/httpserver-api/api/env.cc
index fcc7fe64..52c251d5 100644
--- a/modules/httpserver-api/api/env.cc
+++ b/modules/httpserver-api/api/env.cc
@@ -20,9 +20,11 @@ using namespace json;
using namespace std;
using namespace env_json;

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::env::init(*routes);
}
+#endif

void init(routes& routes)
{
@@ -46,6 +48,7 @@ void init(routes& routes)
return res;
});

+#if !defined(MONITORING)
setEnv.set_handler([](const_req req) {
string param = req.param.at("var").substr(1);
if (setenv(param.c_str(),
@@ -62,7 +65,7 @@ void init(routes& routes)
}
return "";
});
-
+#endif
}

}
diff --git a/modules/httpserver-api/api/file.cc b/modules/httpserver-api/api/file.cc
index 4b71e687..67a0e28e 100644
--- a/modules/httpserver-api/api/file.cc
+++ b/modules/httpserver-api/api/file.cc
@@ -98,6 +98,7 @@ static string file_name(const string& path)
return path.substr(found + 1);
}

+#if !defined(MONITORING)
/**
* Generate a temporary file name in a target directory
* according to a file name
@@ -152,6 +153,7 @@ static void copy(const std::string& from, const std::string& to)
+ e.code().message());
}
}
+#endif

class get_file_handler : public file_interaction_handler {
virtual void handle(const std::string& path, parameters* params,
@@ -318,6 +320,7 @@ class get_file_handler : public file_interaction_handler {
}
};

+#if !defined(MONITORING)
class del_file_handler : public handler_base {
virtual void handle(const std::string& path, parameters* params,
const http::server::request& req, http::server::reply& rep)
@@ -442,18 +445,23 @@ class put_file_handler : public handler_base {
set_headers(rep, "json");
}
};
+#endif

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::file::init(*routes);
}
+#endif

void init(routes& routes)
{
file_json_init_path("file API");
getFile.set_handler(new get_file_handler());
+#if !defined(MONITORING)
delFile.set_handler(new del_file_handler());
putFile.set_handler(new put_file_handler());
upload.set_handler(new post_file_handler());
+#endif
}

}
diff --git a/modules/httpserver-api/api/fs.cc b/modules/httpserver-api/api/fs.cc
index 4151cd72..94eec77b 100644
--- a/modules/httpserver-api/api/fs.cc
+++ b/modules/httpserver-api/api/fs.cc
@@ -33,9 +33,11 @@ static void fill_dfstat(DFStat& dfstat, const osv::mount_desc& mount, const stru
dfstat.blocksize = st.f_frsize;
}

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::fs::init(*routes);
}
+#endif

void init(routes& routes) {

diff --git a/modules/httpserver-api/api/hardware.cc b/modules/httpserver-api/api/hardware.cc
index cb6957df..f023e394 100644
--- a/modules/httpserver-api/api/hardware.cc
+++ b/modules/httpserver-api/api/hardware.cc
@@ -24,9 +24,11 @@ using namespace std;
using namespace json;
using namespace hardware_json;

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::hardware::init(*routes);
}
+#endif

void init(routes& routes)
{
diff --git a/modules/httpserver-api/api/network.cc b/modules/httpserver-api/api/network.cc
index 3cf460db..7d31faf8 100644
--- a/modules/httpserver-api/api/network.cc
+++ b/modules/httpserver-api/api/network.cc
@@ -30,7 +30,6 @@ static Interface get_interface(const string& name, ifnet* ifp, long time)
Interface f;
interface intf(name);

-
if_data cur_data = { 0 };
if (!set_interface_info(ifp, cur_data, intf)) {
throw server_error_exception("Failed getting interface information");
@@ -43,9 +42,11 @@ static Interface get_interface(const string& name, ifnet* ifp, long time)
return f;
}

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::network::init(*routes);
}
+#endif

/**
* Initialize the routes object with specific routes mapping
diff --git a/modules/httpserver-api/api/os.cc b/modules/httpserver-api/api/os.cc
index d80ab1ba..036f9162 100644
--- a/modules/httpserver-api/api/os.cc
+++ b/modules/httpserver-api/api/os.cc
@@ -33,9 +33,11 @@ using namespace std;
using namespace json;
using namespace os_json;

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::os::init(*routes);
}
+#endif

void init(routes& routes)
{
@@ -83,6 +85,7 @@ void init(routes& routes)
return memory::get_balloon_size();
});

+#if !defined(MONITORING)
os_shutdown.set_handler([](const_req req) {
osv::shutdown();
return "";
@@ -97,6 +100,7 @@ void init(routes& routes)
osv::reboot();
return "";
});
+#endif

os_dmesg.set_handler([](const_req req) {
return debug_buffer;
@@ -109,11 +113,13 @@ void init(routes& routes)
return json_return_type(hostname);
});

+#if !defined(MONITORING)
os_set_hostname.set_handler([](const_req req) {
string hostname = req.get_query_param("name");
sethostname(hostname.c_str(), hostname.size());
return "";
});
+#endif

os_threads.set_handler([](const_req req) {
using namespace std::chrono;
@@ -143,6 +149,7 @@ void init(routes& routes)
return osv::getcmdline();
});

+#if !defined(MONITORING)
os_set_cmdline.set_handler([](const_req req) {
string newcmd = req.get_query_param("cmdline");

@@ -157,6 +164,7 @@ void init(routes& routes)
return osv::getcmdline();

});
+#endif

}

diff --git a/modules/httpserver-api/api/trace.cc b/modules/httpserver-api/api/trace.cc
index f871e65f..bdea68ee 100644
--- a/modules/httpserver-api/api/trace.cc
+++ b/modules/httpserver-api/api/trace.cc
@@ -25,7 +25,7 @@ using namespace httpserver::json::trace_json;
static std::unordered_map<tracepoint_base*,
std::unique_ptr<tracepoint_counter>> counters;

-extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
+extern "C" void __attribute__((visibility("default"))) httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::trace::init(*routes);
}

diff --git a/modules/httpserver-api/global_server.cc b/modules/httpserver-api/global_server.cc
index f56f4ed4..5cf1c62e 100644
--- a/modules/httpserver-api/global_server.cc
+++ b/modules/httpserver-api/global_server.cc
@@ -13,11 +13,21 @@
#include <dlfcn.h>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
-#include "yaml-cpp/yaml.h"
#include "json/api_docs.hh"
#include <osv/debug.h>
#include "transformers.hh"
#include <osv/options.hh>
+#if !defined(MONITORING)
+#include "yaml-cpp/yaml.h"
+#else
+#include "api/os.hh"
+#include "api/fs.hh"
+#include "api/file.hh"
+#include "api/network.hh"
+#include "api/hardware.hh"
+#include "api/api.hh"
+#include "api/env.hh"
+#endif

namespace httpserver {

@@ -36,6 +46,7 @@ bool global_server::run(std::map<std::string,std::vector<std::string>>& _config)
if (get().s != nullptr) {
return false;
}
+#if !defined(MONITORING)
std::string config_file_path = "/tmp/httpserver.conf";
if (options::option_value_exists(_config, "config-file")) {
config_file_path = options::extract_option_value(_config, "config-file");
@@ -79,16 +90,19 @@ bool global_server::run(std::map<std::string,std::vector<std::string>>& _config)
throw e;
}
}
+#endif

set(_config);
get().set("ipaddress", "0.0.0.0");
get().set("port", "8000");

+#if !defined(MONITORING)
if (get().config.count("ssl")) {
get().set("cert", "/etc/pki/server.pem");
get().set("key", "/etc/pki/private/server.key");
get().set("cacert", "/etc/pki/CA/cacert.pem");
}
+#endif

auto port = get().config["port"][0];
get().s = new http::server::server(get().config, &get()._routes);
@@ -105,6 +119,7 @@ bool global_server::run(std::map<std::string,std::vector<std::string>>& _config)
return true;
}

+#if !defined(MONITORING)
void global_server::setup_file_mappings(const YAML::Node& file_mappings_node) {
for (auto node : file_mappings_node) {
const YAML::Node path = node["path"];
@@ -154,6 +169,7 @@ void global_server::setup_redirects(const YAML::Node& redirects_node) {
}
}
}
+#endif

global_server::global_server()
: s(nullptr)
@@ -182,7 +198,15 @@ void global_server::set_routes()
{
path_holder::set_routes(&_routes);
json::api_doc_init(_routes);
-
+#if defined(MONITORING)
+ httpserver::api::api::init(_routes);
+ httpserver::api::fs::init(_routes);
+ httpserver::api::os::init(_routes);
+ httpserver::api::network::init(_routes);
+ httpserver::api::hardware::init(_routes);
+ httpserver::api::env::init(_routes);
+ httpserver::api::file::init(_routes);
+#endif
{
namespace fs = boost::filesystem;
fs::path plugin_path("/usr/mgmt/plugins/");
diff --git a/modules/httpserver-api/global_server.hh b/modules/httpserver-api/global_server.hh
index 105b1ccb..e70b7854 100644
--- a/modules/httpserver-api/global_server.hh
+++ b/modules/httpserver-api/global_server.hh
@@ -12,7 +12,9 @@
#include <vector>
#include <mutex>
#include <condition_variable>
+#if !defined(MONITORING)
#include <yaml-cpp/node/iterator.h>
+#endif

namespace httpserver {
/**
@@ -64,8 +66,10 @@ private:

global_server();
void set_routes();
+#if !defined(MONITORING)
void setup_redirects(const YAML::Node& node);
void setup_file_mappings(const YAML::Node& node);
+#endif
void load_plugin(const std::string& path);
static global_server* instance;
routes _routes;
diff --git a/modules/httpserver-api/handlers.cc b/modules/httpserver-api/handlers.cc
index be5dfc92..11cb8c91 100644
--- a/modules/httpserver-api/handlers.cc
+++ b/modules/httpserver-api/handlers.cc
@@ -19,6 +19,7 @@ const std::string handler_base::ERROR_500_PAGE("<h1>Something went wrong</h1>");
const std::string handler_base::ERROR_404_PAGE(
"<h1>We didn't find the page you were looking for</h1>");

+#pragma GCC visibility push(default)
void handler_base::set_headers_explicit(http::server::reply& rep, const std::string& mime)
{
const int contentLength = rep.content.size();
@@ -53,6 +54,7 @@ void handler_base::reply500(http::server::reply& rep, const std::string& alterna
rep = http::server::reply::stock_reply(http::server::reply::internal_server_error,
&alternative_message);
}
+#pragma GCC visibility pop

directory_handler::directory_handler(const string& doc_root,
file_transformer* transformer)
@@ -76,6 +78,7 @@ void directory_handler::handle(const string& path, parameters* parts,
read(full_path, req, rep);
}

+#pragma GCC visibility push(default)
file_interaction_handler::~file_interaction_handler()
{
delete transformer;
@@ -128,6 +131,7 @@ bool file_interaction_handler::redirect_if_needed(
}
return false;
}
+#pragma GCC visibility pop

void file_handler::handle(const string& path, parameters* parts,
const http::server::request& req, http::server::reply& rep)
diff --git a/modules/httpserver-api/handlers.hh b/modules/httpserver-api/handlers.hh
index d4ae0fe1..6a4b2c9f 100644
--- a/modules/httpserver-api/handlers.hh
+++ b/modules/httpserver-api/handlers.hh
@@ -32,6 +32,7 @@ typedef const http::server::request& const_req;
* file_handler - map a url path to a file
* function_handler - uses a lambda expression to handle a reqeust
*/
+#pragma GCC visibility push(default)
class handler_base {
public:
/**
@@ -90,7 +91,7 @@ public:
virtual void reply500(http::server::reply& rep,
const std::string& alternative_message = ERROR_500_PAGE);

- /**
+ /**
* Add a mandatory parameter
* @param param a parameter name
* @return a reference to the handler
@@ -107,6 +108,7 @@ public:
std::vector<std::string> mandatory_param;

};
+#pragma GCC visibility pop

/**
* This is a base class for file transformer.
@@ -132,6 +134,7 @@ public:
virtual ~file_transformer() = default;
};

+#pragma GCC visibility push(default)
/**
* A base class for handlers that interact with files.
* directory and file handlers both share some common logic
@@ -188,6 +191,7 @@ protected:
http::server::reply& rep);
file_transformer* transformer;
};
+#pragma GCC visibility pop

/**
* The directory handler get a disk path in the
diff --git a/modules/httpserver-api/json/api_docs.cc b/modules/httpserver-api/json/api_docs.cc
index 0f83b7d4..fad6fefa 100644
--- a/modules/httpserver-api/json/api_docs.cc
+++ b/modules/httpserver-api/json/api_docs.cc
@@ -43,9 +43,11 @@ public:
const string api_registry::base_path = "/api-doc";
static api_registry* registry = nullptr;

+#pragma GCC visibility push(default)
void register_api(const std::string& api, const std::string& description) {
registry->reg(api, description);
}
+#pragma GCC visibility pop

void api_doc_init(routes& _routes) {
registry = new api_registry(_routes);
diff --git a/modules/httpserver-api/json/api_docs.hh b/modules/httpserver-api/json/api_docs.hh
index a3d1f863..990c0409 100644
--- a/modules/httpserver-api/json/api_docs.hh
+++ b/modules/httpserver-api/json/api_docs.hh
@@ -87,8 +87,10 @@ struct api_docs : public json::json_base {
}
};

+#pragma GCC visibility push(default)
void register_api(const std::string& api,
const std::string& description);
+#pragma GCC visibility pop

/**
* Initialize the routes object with specific routes mapping
diff --git a/modules/httpserver-api/json/formatter.cc b/modules/httpserver-api/json/formatter.cc
index bf5a034d..c0691dd4 100644
--- a/modules/httpserver-api/json/formatter.cc
+++ b/modules/httpserver-api/json/formatter.cc
@@ -13,6 +13,7 @@

using namespace std;

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -97,3 +98,4 @@ std::string formatter::json_escape_UTF8_string(const std::string& utf8_string) {

}
}
+#pragma GCC visibility pop
diff --git a/modules/httpserver-api/json/formatter.hh b/modules/httpserver-api/json/formatter.hh
index 24adf1b9..81f56513 100644
--- a/modules/httpserver-api/json/formatter.hh
+++ b/modules/httpserver-api/json/formatter.hh
@@ -12,6 +12,7 @@
#include <time.h>
#include <sstream>

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -124,4 +125,5 @@ private:

}
}
+#pragma GCC visibility pop
#endif /* FORMATTER_HH_ */
diff --git a/modules/httpserver-api/json/json_elements.cc b/modules/httpserver-api/json/json_elements.cc
index 8c1b1711..fbae58ec 100644
--- a/modules/httpserver-api/json/json_elements.cc
+++ b/modules/httpserver-api/json/json_elements.cc
@@ -13,6 +13,7 @@

using namespace std;

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -107,3 +108,4 @@ bool json_base::is_verify() const

}
}
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/json/json_elements.hh b/modules/httpserver-api/json/json_elements.hh
index 9336572f..87dfcd9c 100644
--- a/modules/httpserver-api/json/json_elements.hh
+++ b/modules/httpserver-api/json/json_elements.hh
@@ -14,6 +14,7 @@
#include <sstream>
#include "formatter.hh"

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -224,4 +225,5 @@ struct json_return_type {
}

}
+#pragma GCC visibility pop
#endif /* JSON_ELEMENTS_HH_ */
diff --git a/modules/httpserver-api/json/json_path.cc b/modules/httpserver-api/json/json_path.cc
index fe599994..497b5e2c 100644
--- a/modules/httpserver-api/json/json_path.cc
+++ b/modules/httpserver-api/json/json_path.cc
@@ -7,6 +7,7 @@

#include "json_path.hh"

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -36,3 +37,4 @@ path_description* path_description::get(string nickname)
}

}
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/json/json_path.hh b/modules/httpserver-api/json/json_path.hh
index da91ac41..4f666e64 100644
--- a/modules/httpserver-api/json/json_path.hh
+++ b/modules/httpserver-api/json/json_path.hh
@@ -14,6 +14,7 @@
#include <tuple>
#include "common.hh"

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -142,4 +143,5 @@ struct path_description {

}
}
+#pragma GCC visibility pop
#endif /* JSON_PATH_HH_ */
diff --git a/modules/httpserver-api/main.cc b/modules/httpserver-api/main.cc
index 2eca5076..00f40b75 100644
--- a/modules/httpserver-api/main.cc
+++ b/modules/httpserver-api/main.cc
@@ -40,7 +40,7 @@ static void handle_parse_error(const std::string &message)
exit(1);
}

-int main(int argc, char* argv[])
+int __attribute__((visibility("default"))) main(int argc, char* argv[])
{
auto options_values = options::parse_options_values(argc - 1, argv + 1, handle_parse_error);

diff --git a/modules/httpserver-api/path_holder.cc b/modules/httpserver-api/path_holder.cc
index 17d71066..a4cd5588 100644
--- a/modules/httpserver-api/path_holder.cc
+++ b/modules/httpserver-api/path_holder.cc
@@ -7,6 +7,7 @@
#include "path_holder.hh"
#include <assert.h>

+#pragma GCC visibility push(default)
namespace httpserver {

routes* path_holder::_routes = nullptr;
@@ -36,3 +37,4 @@ handler_base& path_holder::set_handler(const json_request_function& fun) const
}

}
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/path_holder.hh b/modules/httpserver-api/path_holder.hh
index aa393f6d..239c1aa0 100644
--- a/modules/httpserver-api/path_holder.hh
+++ b/modules/httpserver-api/path_holder.hh
@@ -11,6 +11,7 @@
#include "handlers.hh"
#include "routes.hh"

+#pragma GCC visibility push(default)
namespace httpserver {
/**
* Path holder glues handlers and implementation.
@@ -89,5 +90,5 @@ private:
};

}
-
+#pragma GCC visibility pop
#endif /* PATH_HOLDER_HH_ */
diff --git a/modules/httpserver-api/reply.cc b/modules/httpserver-api/reply.cc
index 9f81e8d3..e13737aa 100644
--- a/modules/httpserver-api/reply.cc
+++ b/modules/httpserver-api/reply.cc
@@ -11,6 +11,7 @@
#include "reply.hh"
#include <string>

+#pragma GCC visibility push(default)
namespace http {

namespace server {
@@ -239,3 +240,4 @@ reply& reply::add_header(const std::string& h, const std::string& value) {
} // namespace server

} // namespace http
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/reply.hh b/modules/httpserver-api/reply.hh
index f95e6de2..ceb23983 100644
--- a/modules/httpserver-api/reply.hh
+++ b/modules/httpserver-api/reply.hh
@@ -17,6 +17,7 @@
#include <vector>
#include <boost/asio.hpp>

+#pragma GCC visibility push(default)
namespace http {

namespace server {
@@ -87,4 +88,5 @@ struct reply {

} // namespace http

+#pragma GCC visibility pop
#endif // HTTP_REPLY_HPP
diff --git a/modules/httpserver-api/server.cc b/modules/httpserver-api/server.cc
index 709972b2..b508b9c7 100644
--- a/modules/httpserver-api/server.cc
+++ b/modules/httpserver-api/server.cc
@@ -11,23 +11,29 @@

#include "server.hh"
#include "connection.hh"
+#if !defined(MONITORING)
#include "ssl_server.hh"
#include "openssl-init.hh"
+#endif
#include "plain_server.hh"

#include <utility>
+#if !defined(MONITORING)
#include <openssl/ssl.h>
+#endif
#include <osv/options.hh>

namespace http {

namespace server {

+#if !defined(MONITORING)
static bool exists(const std::string& path)
{
struct stat s;
return stat(path.c_str(), &s) == 0;
}
+#endif

server::server(std::map<std::string,std::vector<std::string>> &config,
httpserver::routes* routes)
@@ -48,6 +54,7 @@ server::server(std::map<std::string,std::vector<std::string>> &config,
tcp_acceptor.bind(endpoint);
tcp_acceptor.listen();

+#if !defined(MONITORING)
if (options::extract_option_flag(config, "ssl", [](const std::string &message) {
std::cerr << message << std::endl;
throw std::runtime_error("invalid configuration");
@@ -81,6 +88,7 @@ server::server(std::map<std::string,std::vector<std::string>> &config,
ssl::context ctx = make_ssl_context(ca_cert_path, cert_path, key_path);
acceptor_.reset(new ssl_acceptor(io_service_, std::move(ctx), std::move(tcp_acceptor)));
} else {
+#endif
if (!config.empty()) {
for (auto option : config) {
std::cout << "Unrecognized option: " << option.first << std::endl;
@@ -89,7 +97,9 @@ server::server(std::map<std::string,std::vector<std::string>> &config,
}

acceptor_.reset(new plain_acceptor(io_service_, std::move(tcp_acceptor)));
+#if !defined(MONITORING)
}
+#endif

acceptor_->do_accept(std::bind(&server::on_connected, this, std::placeholders::_1));
}
--
2.20.1

Waldemar Kozaczuk

unread,
Mar 19, 2020, 4:45:20 PM3/19/20
to osv...@googlegroups.com, Waldemar Kozaczuk
This patch adds new module httpserver-monitoring-api that provides
read-only subset of the API intended for monitoring purposes only.
The monitoring flavor of httpserver provides following routes:

- /api
- /env
- /file
- /fs
- /hardware
- /network
- /os

where each of the routes expose GET-only paths.
The monitoring module disables YAML file based configuration
as well as SSL connectivity.

It is also possible to add trace plugin - libhttpserver-api_trace.so.

Please note that monitoring module re-uses the same httpserver-api
source code however when compiling we pass '-DMONITORING' flag
to disable subset of code not needed for monitoring functionality.
We also compile the code with '-fvisibility=hidden' to further reduce
size of the monitoring executable (<800K).

Fixes #820

Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>
---
modules/httpserver-monitoring-api/.gitignore | 4 +
modules/httpserver-monitoring-api/Makefile | 84 +++++
.../api-doc/listings/api.json | 40 +++
.../api-doc/listings/env.json | 57 ++++
.../api-doc/listings/file.json | 187 ++++++++++++
.../api-doc/listings/fs.json | 98 ++++++
.../api-doc/listings/hardware.json | 73 +++++
.../api-doc/listings/network.json | 266 ++++++++++++++++
.../api-doc/listings/os.json | 289 ++++++++++++++++++
modules/httpserver-monitoring-api/module.py | 24 ++
.../mpm/package.yaml | 1 +
11 files changed, 1123 insertions(+)
create mode 100644 modules/httpserver-monitoring-api/.gitignore
create mode 100644 modules/httpserver-monitoring-api/Makefile
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/api.json
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/env.json
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/file.json
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/fs.json
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/hardware.json
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/network.json
create mode 100644 modules/httpserver-monitoring-api/api-doc/listings/os.json
create mode 100644 modules/httpserver-monitoring-api/module.py
create mode 100644 modules/httpserver-monitoring-api/mpm/package.yaml

diff --git a/modules/httpserver-monitoring-api/.gitignore b/modules/httpserver-monitoring-api/.gitignore
new file mode 100644
index 00000000..8d8365ad
--- /dev/null
+++ b/modules/httpserver-monitoring-api/.gitignore
@@ -0,0 +1,4 @@
+*.so
+autogen/*
+obj/*
+*.manifest
diff --git a/modules/httpserver-monitoring-api/Makefile b/modules/httpserver-monitoring-api/Makefile
new file mode 100644
index 00000000..b9abc628
--- /dev/null
+++ b/modules/httpserver-monitoring-api/Makefile
@@ -0,0 +1,84 @@
+SRC = $(shell readlink -f ../..)
+include $(SRC)/modules/java-base/common.gmk
+INCLUDES += -I. -I../httpserver-api -I $(SRC)/libc/internal
+
+# compiler flags:
+# -g adds debugging information to the executable file
+# -Wall turns on most, but not all, compiler warnings
+autodepend = -MD -MT $@ -MP
+CXXFLAGS = -g -Wall -std=c++11 -fPIC $(INCLUDES) -O2 $(autodepend) -DMONITORING -fvisibility=hidden
+#TODO: Figure out why lto breaks exception handling
+#CXXFLAGS = -g -Wall -std=c++11 -fPIC $(INCLUDES) -O2 $(autodepend) -DMONITORING -fvisibility=hidden -flto
+#LDFLAGS = -flto
+src = $(shell readlink -f ../..)
+
+ifndef ARCH
+ ARCH = x64
+endif
+
+ifndef mode
+ mode = release
+endif
+
+ifndef OSV_BUILD_PATH
+ OSV_BUILD_PATH = $(src)/build/$(mode).$(ARCH)
+endif
+boost-libs := -lboost_system -lboost_filesystem
+
+# the build target executable:
+TARGET = httpserver-api
+JSON_FILES := $(wildcard api-doc/listings/*.json)
+JSON_CC_FILES := $(subst .json,.json.cc,$(subst api-doc/listings/,autogen/,$(JSON_FILES)))
+CPP_FILES := $(addprefix json/,$(notdir $(wildcard ../httpserver-api/json/*.cc))) \
+ api/fs.cc api/os.cc api/network.cc api/hardware.cc api/env.cc api/file.cc api/api.cc \
+ common.cc main.cc plain_server.cc server.cc connection.cc matcher.cc \
+ reply.cc connection_manager.cc mime_types.cc request_handler.cc \
+ transformers.cc global_server.cc request_parser.cc handlers.cc \
+ path_holder.cc routes.cc $(JSON_CC_FILES)
+OBJ_FILES := $(addprefix obj/,$(CPP_FILES:.cc=.o))
+
+DYN_LIBS = -lpthread -ldl -L../libtools -ltools $(boost-libs)
+
+LIBS = $(DYN_LIBS) $(STATIC_LIBS)
+
+quiet = $(if $V, $1, @echo " $2"; $1)
+very-quiet = $(if $V, $1, @$1)
+
+DEPS := $(OBJ_FILES:.o=.d)
+
+module: all
+
+all: lib$(TARGET).so
+ $(call very-quiet, $(SRC)/scripts/manifest_from_host.sh lib$(TARGET).so > usr.manifest)
+
+lib$(TARGET).so: $(OBJ_FILES)
+ $(call quiet, $(CXX) $(CXXFLAGS) -shared $(STATIC_LIBS) -o $@ $^ $(DYN_LIBS), LINK $@)
+
+ifneq ($(MAKECMDGOALS),clean)
+-include $(DEPS)
+endif
+
+autogen/%.cc: api-doc/listings/% ../httpserver-api/json2code.py
+ $(call very-quiet, mkdir -p autogen)
+ $(call quiet,../httpserver-api/json2code.py -outdir autogen -f $< -ns json, GEN $@)
+
+$(OBJ_FILES): obj/%.o: ../httpserver-api/%.cc
+ $(call very-quiet, mkdir -p obj/stub obj/json obj/api obj/autogen)
+ $(call quiet, $(CXX) $(CXXFLAGS) -c -MMD -o $@ $<, CXX $@)
+
+clean:
+ $(call quiet, $(RM) -f $(TARGET), CLEAN)
+ $(call very-quiet, $(RM) -f lib*.so)
+ $(call very-quiet, $(RM) -rf obj)
+ $(call very-quiet, $(RM) -rf autogen)
+ $(call very-quiet, $(RM) -f *usr*.manifest)
+
+check:
+ # Test plain readonly HTTP
+ cd $(src) && \
+ make image=httpserver-monitoring-api.fg && \
+ PYTHONPATH=$(src)/scripts modules/httpserver-api/tests/testhttpserver-monitoring-api.py
+
+.PHONY: check
+
+.SECONDARY:
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/api.json b/modules/httpserver-monitoring-api/api-doc/listings/api.json
new file mode 100644
index 00000000..ec18bd9f
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/api.json
@@ -0,0 +1,40 @@
+{
+ "apiVersion":"0.0.1",
+ "swaggerVersion":"1.2",
+ "basePath":"{{Protocol}}://{{Host}}",
+ "resourcePath":"/api",
+ "produces":[
+ "application/json"
+ ],
+ "apis":[
+ {
+ "path":"/api/batch",
+ "operations":[
+ {
+ "method":"POST",
+ "summary":"Batch process API calls",
+ "notes":"Perform batch API calls in a single command. Commands are performed sequentially and independently. Each command has its own response code",
+ "type":"string",
+ "errorResponses":[
+ {
+ "code":400,
+ "reason":"Bad request"
+ }
+ ],
+ "nickname":"api_batch",
+ "produces":[
+ "application/json"
+ ],
+ "parameters":[
+ {
+ "name":"batch",
+ "paramType":"form",
+ "type":"string",
+ "required":true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/env.json b/modules/httpserver-monitoring-api/api-doc/listings/env.json
new file mode 100644
index 00000000..29ce4384
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/env.json
@@ -0,0 +1,57 @@
+{
+ "apiVersion":"0.0.1",
+ "swaggerVersion":"1.2",
+ "basePath":"{{Protocol}}://{{Host}}",
+ "resourcePath":"/env",
+ "produces":[
+ "application/json"
+ ],
+ "apis":[
+ {
+ "path":"/env/{var}",
+ "operations":[
+ {
+ "method":"GET",
+ "summary":"Get an environment variable",
+ "notes":"return the environment variable value",
+ "type":"string",
+ "errorResponses":[
+ {
+ "code":400,
+ "reason":"Variable not found"
+ }
+ ],
+ "nickname":"getEnv",
+ "produces":[
+ "application/json"
+ ],
+ "parameters":[
+ {
+ "name":"var",
+ "description":"name of the environment variable",
+ "required":true,
+ "allowMultiple":true,
+ "type":"string",
+ "paramType":"path"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "path":"/env/",
+ "operations":[
+ {
+ "method":"GET",
+ "summary":"Returns a list of all environment variables in the system.",
+ "type":"array",
+ "items": {"type": "string"},
+ "nickname":"list_env",
+ "produces":[
+ "application/json"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/file.json b/modules/httpserver-monitoring-api/api-doc/listings/file.json
new file mode 100644
index 00000000..7ab8a0d0
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/file.json
@@ -0,0 +1,187 @@
+{
+ "apiVersion":"0.0.1",
+ "swaggerVersion":"1.2",
+ "basePath":"{{Protocol}}://{{Host}}",
+ "resourcePath":"/file",
+ "produces":[
+ "application/json"
+ ],
+ "apis":[
+ {
+ "path":"/file/{path-par}",
+ "operations":[
+ {
+ "method":"GET",
+ "summary":"Get File/Directory information",
+ "notes":"return File or Directory related information",
+ "type":"string",
+ "errorResponses":[
+ {
+ "code":404,
+ "reason":"File not found"
+ },
+ {
+ "code":400,
+ "reason":"Bad Request"
+ }
+ ],
+ "nickname":"getFile",
+ "produces":[
+ "application/json"
+ ],
+ "parameters":[
+ {
+ "name":"path-par",
+ "description":"Full path of file or directory",
+ "required":true,
+ "allowMultiple":true,
+ "type":"string",
+ "paramType":"path"
+ },
+ {
+ "name":"op",
+ "description":"The operation to perform",
+ "required":true,
+ "allowMultiple":false,
+ "type":"string",
+ "paramType":"query",
+ "enum":["GET", "LISTSTATUS", "GETFILESTATUS"]
+ },
+ {
+ "name":"offset",
+ "description":"Offset in a file",
+ "required":false,
+ "allowMultiple":false,
+ "type":"long",
+ "paramType":"query"
+ },
+ {
+ "name":"length",
+ "description":"The number of bytes to be processed.",
+ "required":false,
+ "allowMultiple":false,
+ "type":"long",
+ "paramType":"query"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "models":{
+ "ContentSummary":{
+ "id": "ContentSummary",
+ "properties":{
+ "directoryCount":{
+ "description":"The number of directories.",
+ "type":"int",
+ "required":true
+ },
+ "fileCount":{
+ "description":"The number of files.",
+ "type":"int",
+ "required":true
+ },
+ "length":{
+ "description":"The number of bytes used by the content.",
+ "type":"int",
+ "required":true
+ },
+ "quota":{
+ "description":"The namespace quota of this directory.",
+ "type":"int",
+ "required":true
+ },
+ "spaceConsumed":{
+ "description":"The disk space consumed by the content.",
+ "type":"int",
+ "required":true
+ },
+ "spaceQuota":{
+ "description":"The disk space quota.",
+ "type":"int",
+ "required":true
+ }
+ }
+ },
+ "FileChecksum":{
+ "id": "FileChecksum",
+ "properties":{
+ "algorithm":{
+ "description":"The name of the checksum algorithm.",
+ "type":"string",
+ "required":true
+ },
+ "bytes":{
+ "description":"The byte sequence of the checksum in hexadecimal.",
+ "type":"string",
+ "required":true
+ },
+ "length":{
+ "description":"The length of the bytes (not the length of the string).",
+ "type":"int",
+ "required":true
+ }
+ }
+ },
+ "FileStatusProperties":{
+ "id": "FileStatusProperties",
+ "properties":{
+ "accessTime":{
+ "description":"The access time.",
+ "type":"int",
+ "required":true
+ },
+ "blockSize":{
+ "description":"The block size of a file.",
+ "type":"int",
+ "required":true
+ },
+ "group":{
+ "description":"The group owner.",
+ "type":"string",
+ "required":true
+ },
+ "length":{
+ "description":"The number of bytes in a file.",
+ "type":"long",
+ "required":true
+ },
+ "modificationTime":{
+ "description":"The modification time.",
+ "type":"int",
+ "required":true
+ },
+ "owner":{
+ "description":"The user who is the owner.",
+ "type":"string",
+ "required":true
+ },
+ "pathSuffix":{
+ "description":"The path suffix.",
+ "type":"string",
+ "required":true
+ },
+ "permission":{
+ "description":"The permission represented as a octal string.",
+ "type":"string",
+ "required":true
+ },
+ "replication":{
+ "description":"The number of replication of a file.",
+ "type":"int",
+ "required":true
+ },
+ "symlink":{
+ "description":"The link target of a symlink.",
+ "type":"string"
+ },
+ "type":{
+ "description":"The type of the path object.",
+ "type":"string",
+ "required":true
+ }
+ }
+ }
+ }
+}
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/fs.json b/modules/httpserver-monitoring-api/api-doc/listings/fs.json
new file mode 100644
index 00000000..7fd2062b
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/fs.json
@@ -0,0 +1,98 @@
+{
+ "apiVersion": "0.0.1",
+ "swaggerVersion": "1.2",
+ "basePath": "{{Protocol}}://{{Host}}",
+ "resourcePath": "/fs",
+ "produces": [
+ "application/json"
+ ],
+ "apis": [
+ {
+ "path": "/fs/df/{mount}",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "report a file system usage",
+ "type": "array",
+ "items": {
+ "type": "DFStat"
+ },
+ "errorResponses":[
+ {
+ "code":404,
+ "reason":"File system not found"
+ }
+ ],
+ "nickname" : "getDFStats",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name":"mount",
+ "description":"mount point",
+ "required":false,
+ "allowMultiple":true,
+ "type":"string",
+ "paramType":"path"
+ }
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/fs/df/",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Return all file systems",
+ "type": "array",
+ "items": {
+ "$ref": "DFStat"
+ },
+ "nickname" : "list_df_stats",
+ "produces": [
+ "application/json"
+ ]
+ }
+ ]
+ }
+ ],
+ "models" : {
+ "DFStat": {
+ "description": "Information on one file system",
+ "id": "DFStat",
+ "properties": {
+ "filesystem" : {
+ "type": "string",
+ "description": "fs name"
+ },
+ "mount" : {
+ "type": "string",
+ "description": "mount point"
+ },
+ "btotal" : {
+ "type": "long",
+ "description": "total data blocks in file system"
+ },
+ "bfree" : {
+ "type": "long",
+ "description": "free blocks in file system"
+ },
+ "ftotal" : {
+ "type": "long",
+ "description": "total file nodes in file system"
+ },
+ "ffree" : {
+ "type": "long",
+ "description": "free file nodes in file system"
+ },
+ "blocksize" : {
+ "type": "long",
+ "description": "block size in bytes"
+ }
+ }
+ }
+ }
+}
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/hardware.json b/modules/httpserver-monitoring-api/api-doc/listings/hardware.json
new file mode 100644
index 00000000..eaf26279
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/hardware.json
@@ -0,0 +1,73 @@
+{
+ "apiVersion": "0.0.1",
+ "swaggerVersion": "1.2",
+ "basePath": "{{Protocol}}://{{Host}}",
+ "resourcePath": "/hardware",
+ "produces": [
+ "application/json"
+ ],
+ "apis": [
+ {
+ "path": "/hardware/processor/flags",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "list all present processor features",
+ "type": "string",
+ "nickname" : "processorFeatures",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/hardware/firmware/vendor",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Return the firmware vendor",
+ "type": "string",
+ "nickname" : "firmware_vendor",
+ "produces": [
+ "application/json"
+ ]
+ }
+ ]
+ },
+ {
+ "path": "/hardware/hypervisor",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns name of the hypervisor OSv is running on.",
+ "type": "string",
+ "nickname" : "hypervisor_name",
+ "produces": [
+ "application/json"
+ ]
+ }
+ ]
+ },
+ {
+ "path": "/hardware/processor/count",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "list the current number of processors",
+ "type": "long",
+ "nickname" : "processorCount",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ }
+ ]
+}
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/network.json b/modules/httpserver-monitoring-api/api-doc/listings/network.json
new file mode 100644
index 00000000..0d082f3e
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/network.json
@@ -0,0 +1,266 @@
+{
+ "apiVersion":"0.0.1",
+ "swaggerVersion":"1.2",
+ "basePath":"{{Protocol}}://{{Host}}",
+ "resourcePath":"/network",
+ "produces":[
+ "application/json"
+ ],
+ "apis":[
+ {
+ "path":"/network/ifconfig/",
+ "operations":[
+ {
+ "method":"GET",
+ "summary":"Get all interfaces",
+ "notes":"Get a list of all the interfaces configuration and data",
+ "type":"array",
+ "items": {"type": "Interface"},
+ "nickname":"listIfconfig",
+ "produces":[
+ "application/json"
+ ],
+ "parameters":[
+ ]
+ }
+ ]
+ },
+ {
+ "path":"/network/ifconfig/{intf}",
+ "operations":[
+ {
+ "method":"GET",
+ "summary":"Get an interface",
+ "notes":"Get an interface configuration and data",
+ "type":"Interface",
+ "errorResponses":[
+ {
+ "code":404,
+ "reason":"Interface not found"
+ }
+ ],
+ "nickname":"getIfconfig",
+ "produces":[
+ "application/json"
+ ],
+ "parameters":[
+ {
+ "name":"intf",
+ "description":"name of the interface",
+ "required":true,
+ "allowMultiple":false,
+ "type":"string",
+ "paramType":"path"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "path":"/network/route/",
+ "operations":[
+ {
+ "method":"GET",
+ "summary":"Get the ip route table",
+ "notes":"Get a list of the available routes",
+ "type":"Routes",
+ "items": {"type": "Route"},
+ "nickname":"getRoute",
+ "produces":[
+ "application/json"
+ ],
+ "parameters":[
+
+ ]
+ }
+ ]
+ }
+ ],
+ "models":{
+ "Interface_config":{
+ "id": "Interface_config",
+ "properties":{
+ "name":{
+ "type":"string"
+ },
+ "addr":{
+ "type":"string"
+ },
+ "mask":{
+ "type":"string"
+ },
+ "broadcast":{
+ "type":"string"
+ },
+ "flags":{
+ "type":"string"
+ },
+ "mtu":{
+ "type":"string"
+ },
+ "phys_addr":{
+ "type":"string"
+ }
+ }
+ },
+ "Wakeup_stats":{
+ "id": "Wakeup_stats",
+ "properties":{
+ "packets_8":{
+ "type":"long"
+ },
+ "packets_16":{
+ "type":"long"
+ },
+ "packets_32":{
+ "type":"long"
+ },
+ "packets_64":{
+ "type":"long"
+ },
+ "packets_128":{
+ "type":"long"
+ },
+ "packets_256":{
+ "type":"long"
+ }
+ }
+ },
+ "Interface_data":{
+ "id": "Interface_data",
+ "properties":{
+ "ifi_type":{
+ "type":"char"
+ },
+ "ifi_physical":{
+ "type":"char"
+ },
+ "ifi_addrlen":{
+ "type":"char"
+ },
+ "ifi_hdrlen":{
+ "type":"char"
+ },
+ "ifi_link_state":{
+ "type":"char"
+ },
+ "ifi_spare_char1":{
+ "type":"char"
+ },
+ "ifi_spare_char2":{
+ "type":"char"
+ },
+ "ifi_datalen":{
+ "type":"char"
+ },
+ "ifi_mtu":{
+ "type":"long"
+ },
+ "ifi_metric":{
+ "type":"long"
+ },
+ "ifi_baudrate":{
+ "type":"long"
+ },
+ "ifi_ipackets":{
+ "type":"long"
+ },
+ "ifi_ierrors":{
+ "type":"long"
+ },
+ "ifi_opackets":{
+ "type":"long"
+ },
+ "ifi_oerrors":{
+ "type":"long"
+ },
+ "ifi_collisions":{
+ "type":"long"
+ },
+ "ifi_ibytes":{
+ "type":"long"
+ },
+ "ifi_obytes":{
+ "type":"long"
+ },
+ "ifi_imcasts":{
+ "type":"long"
+ },
+ "ifi_omcasts":{
+ "type":"long"
+ },
+ "ifi_iqdrops":{
+ "type":"long"
+ },
+ "ifi_noproto":{
+ "type":"long"
+ },
+ "ifi_hwassist":{
+ "type":"long"
+ },
+ "ifi_epoch":{
+ "type":"long"
+ },
+ "ifi_ibh_wakeups":{
+ "type":"long"
+ },
+ "ifi_oworker_kicks":{
+ "type":"long"
+ },
+ "ifi_oworker_wakeups":{
+ "type":"long"
+ },
+ "ifi_oworker_packets":{
+ "type":"long"
+ },
+ "ifi_okicks":{
+ "type":"long"
+ },
+ "ifi_oqueue_is_full":{
+ "type":"long"
+ },
+ "ifi_iwakeup_stats":{
+ "type": "Wakeup_stats"
+ },
+ "ifi_owakeup_stats":{
+ "type": "Wakeup_stats"
+ }
+ }
+ },
+ "Interface":{
+ "id":"Interface",
+ "properties":{
+ "config":{
+ "type":"Interface_config"
+ },
+ "data":{
+ "type":"Interface_data"
+ },
+ "time":{
+ "type":"long",
+ "description":"Time when interface information was taken (microseconds since uptime)"
+ }
+ }
+ },
+ "Route":{
+ "id":"Route",
+ "properties":{
+ "destination":{
+ "type":"string"
+ },
+ "gateway":{
+ "type":"string"
+ },
+ "flags":{
+ "type":"string"
+ },
+ "netif":{
+ "type":"string"
+ },
+ "ipv6":{
+ "type":"boolean"
+ }
+ }
+ }
+ }
+}
diff --git a/modules/httpserver-monitoring-api/api-doc/listings/os.json b/modules/httpserver-monitoring-api/api-doc/listings/os.json
new file mode 100644
index 00000000..a3ecae80
--- /dev/null
+++ b/modules/httpserver-monitoring-api/api-doc/listings/os.json
@@ -0,0 +1,289 @@
+{
+ "apiVersion": "0.0.1",
+ "swaggerVersion": "1.2",
+ "basePath": "{{Protocol}}://{{Host}}",
+ "resourcePath": "/os",
+ "produces": [
+ "application/json"
+ ],
+ "apis": [
+ {
+ "path": "/os/name",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns name of the operating system",
+ "type": "string",
+ "nickname": "os_name",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/version",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns version of the operating system",
+ "type": "string",
+ "nickname": "os_version",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/vendor",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the vendor of the operating system",
+ "type": "string",
+ "nickname": "os_vendor",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/uptime",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the number of seconds since the system was booted",
+ "type": "long",
+ "nickname": "os_uptime",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/date",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the current date and time",
+ "type": "string",
+ "nickname": "os_date",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/memory/total",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns total amount of memory usable by the system (in bytes)",
+ "type": "long",
+ "nickname": "os_memory_total",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/memory/free",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the amount of free memory in the system (in bytes)",
+ "type": "long",
+ "nickname": "os_memory_free",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/memory/balloon",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the JVM balloon size (in bytes)",
+ "type": "long",
+ "nickname": "os_memory_balloon",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ]
+ }
+ ]
+ },
+ {
+ "path": "/os/dmesg",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the operating system boot log",
+ "type": "string",
+ "nickname": "os_dmesg",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/hostname",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the system host name",
+ "type": "string",
+ "nickname": "os_get_hostname",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/threads",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns a list of threads in the system",
+ "type": "Threads",
+ "nickname": "os_threads",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ },
+ {
+ "path": "/os/cmdline",
+ "operations": [
+ {
+ "method": "GET",
+ "summary": "Returns the current boot command line",
+ "notes": "This operation retrieves the current OSv command line. The current command line is either the command line used for the current boot, or the one written by POST.",
+ "type": "string",
+ "nickname": "os_get_cmdline",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ ],
+ "deprecated": "false"
+ }
+ ]
+ }
+ ],
+ "models": {
+ "Thread": {
+ "id": "Thread",
+ "description": "Information on one thread",
+ "properties": {
+ "id": {
+ "type": "long",
+ "description": "32-bit thread id"
+ },
+ "cpu": {
+ "type": "long",
+ "description": "CPU the thread is running on"
+ },
+ "cpu_ms": {
+ "type": "long",
+ "description": "Total CPU time used by the thread (in milliseconds)"
+ },
+ "switches": {
+ "type": "long",
+ "description": "Number of times this thread was context-switched in"
+ },
+ "migrations": {
+ "type": "long",
+ "description": "Number of times this thread was migrated between CPUs"
+ },
+ "preemptions": {
+ "type": "long",
+ "description": "Number of times this thread was preempted (still runnable, but switched out)"
+ },
+ "priority": {
+ "type": "float"
+ },
+ "stack_size": {
+ "type": "long"
+ },
+ "status": {
+ "type": "string",
+ "description": "thread status",
+ "enum": [
+ "invalid",
+ "prestarted",
+ "unstarted",
+ "waiting",
+ "running",
+ "queued",
+ "waking",
+ "terminating",
+ "terminated"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "description": "Thread description (not necessarily unique)"
+ }
+ }
+ },
+ "Threads": {
+ "id": "Threads",
+ "description": "List of threads",
+ "properties": {
+ "list": {
+ "type": "array",
+ "items": {
+ "type": "Thread"
+ },
+ "description": "List of thread objects"
+ },
+ "time_ms": {
+ "type": "long",
+ "description": "Time when thread list was taken (milliseconds since epoche)"
+ }
+ }
+ }
+ }
+}
diff --git a/modules/httpserver-monitoring-api/module.py b/modules/httpserver-monitoring-api/module.py
new file mode 100644
index 00000000..6ae9aae7
--- /dev/null
+++ b/modules/httpserver-monitoring-api/module.py
@@ -0,0 +1,24 @@
+import os
+from osv.modules.api import *
+from osv.modules.filemap import FileMap
+from osv.modules import api
+
+provides = ['httpserver-api']
+
+_module = '${OSV_BASE}/modules/httpserver-monitoring-api'
+
+_exe = '/libhttpserver-api.so'
+
+usr_files = FileMap()
+usr_files.link(_exe).to('/usr/lib/libhttpserver-api.so')
+usr_files.add(os.path.join(_module, 'api-doc')).to('/usr/mgmt/api')
+
+api.require('libtools')
+
+# httpserver will run regardless of an explicit command line
+# passed with "run.py -e".
+daemon = api.run_on_init(_exe + ' --access-allow=true &!')
+
+fg = api.run(_exe + ' --access-allow=true')
+
+default = daemon
diff --git a/modules/httpserver-monitoring-api/mpm/package.yaml b/modules/httpserver-monitoring-api/mpm/package.yaml
new file mode 100644
index 00000000..21023036
--- /dev/null
+++ b/modules/httpserver-monitoring-api/mpm/package.yaml
@@ -0,0 +1 @@
+title: OSv httpserver monitoring APIs
--
2.20.1

Waldemar Kozaczuk

unread,
Mar 19, 2020, 4:45:22 PM3/19/20
to osv...@googlegroups.com, Waldemar Kozaczuk
Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>
---
modules/httpserver-api/tests/basetest.py | 11 +++-
.../tests/monitoring-api/testenv.py | 19 +++++++
.../tests/monitoring-api/testfile.py | 54 +++++++++++++++++++
.../tests/monitoring-api/testfs.py | 18 +++++++
.../tests/monitoring-api/testnetwork.py | 30 +++++++++++
.../tests/monitoring-api/testos.py | 52 ++++++++++++++++++
.../tests/testhttpserver-monitoring-api.py | 41 ++++++++++++++
7 files changed, 223 insertions(+), 2 deletions(-)
create mode 100755 modules/httpserver-api/tests/monitoring-api/testenv.py
create mode 100755 modules/httpserver-api/tests/monitoring-api/testfile.py
create mode 100755 modules/httpserver-api/tests/monitoring-api/testfs.py
create mode 100755 modules/httpserver-api/tests/monitoring-api/testnetwork.py
create mode 100755 modules/httpserver-api/tests/monitoring-api/testos.py
create mode 100755 modules/httpserver-api/tests/testhttpserver-monitoring-api.py

diff --git a/modules/httpserver-api/tests/basetest.py b/modules/httpserver-api/tests/basetest.py
index 40ca64ce..271550c9 100755
--- a/modules/httpserver-api/tests/basetest.py
+++ b/modules/httpserver-api/tests/basetest.py
@@ -85,9 +85,9 @@ class Basetest(unittest.TestCase):
path = self.path_by_nick(api_definition, nickname)
self.assertRegex(self.curl(path), expr)

- def assertHttpError(self, url, code=404):
+ def assertHttpError(self, url, code=404, method='GET', data=None):
try:
- self.curl(url)
+ self.curl(url, method, data)
except HttpError as e:
if e.code != code:
raise Exception('Expected error code %d but got %d' % (code, e.code))
@@ -159,6 +159,13 @@ class Basetest(unittest.TestCase):
raise Exception("Fail to shutdown server")
time.sleep(1)

+ @classmethod
+ def hard_shutdown(cls):
+ child_pid = subprocess.call(['pgrep', "-P", str(cls.os_process.pid)])
+ subprocess.call(['kill', '-9', str(child_pid)])
+ cls.os_process.kill()
+ cls.os_process.wait()
+
@classmethod
def start_image(cls):
if cls.config.check_jvm:
diff --git a/modules/httpserver-api/tests/monitoring-api/testenv.py b/modules/httpserver-api/tests/monitoring-api/testenv.py
new file mode 100755
index 00000000..1b92d76c
--- /dev/null
+++ b/modules/httpserver-api/tests/monitoring-api/testenv.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+import basetest
+
+class testenv(basetest.Basetest):
+ def test_env(self):
+ param = "test-param"
+ get_path = self.path_by_nick(self.env_api, "list_env") + param
+ set_path = get_path + "?val=TEST"
+ self.assertHttpError(set_path, 404, method='POST')
+
+ lst = self.curl(self.path_by_nick(self.env_api, "list_env"))
+ if not "OSV_CPUS=4" in lst:
+ raise Exception('environment variable OSV_CPUS not found in list')
+
+ self.assertHttpError(get_path, 404, method="DELETE")
+
+ @classmethod
+ def setUpClass(cls):
+ cls.env_api = cls.get_json_api("env.json")
diff --git a/modules/httpserver-api/tests/monitoring-api/testfile.py b/modules/httpserver-api/tests/monitoring-api/testfile.py
new file mode 100755
index 00000000..c03c1146
--- /dev/null
+++ b/modules/httpserver-api/tests/monitoring-api/testfile.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+import os
+import urllib.request, urllib.parse, urllib.error
+import basetest
+import subprocess
+
+class testfile(basetest.Basetest):
+ def build_curl_cmd(self, args):
+ if not self._client.is_ssl():
+ return 'curl ' + args
+ return 'curl --cacert %s --cert %s --key %s %s' % (
+ self.get_ca_cert_path(), self.get_client_cert_path(), self.get_client_key_path(), args)
+
+ def build_wget_cmd(self, args):
+ if not self._client.is_ssl():
+ return 'wget ' + args
+ return 'wget --ca-certificate=%s --certificate=%s --private-key=%s %s' % (
+ self.get_ca_cert_path(), self.get_client_cert_path(), self.get_client_key_path(), args)
+
+ def test_list_file_cmd(self):
+ path = "/file"
+ lst = self.curl(path + "/etc?op=LISTSTATUS")
+ hosts = next((item for item in lst if item["pathSuffix"] == "hosts"), None)
+ self.assertEqual(hosts["owner"], "osv")
+
+ def test_list_astrik_file_cmd(self):
+ path = "/file"
+ lst = self.curl(path + "/et%3F/hos*?op=LISTSTATUS")
+ hosts = next((item for item in lst if item["pathSuffix"] == "hosts"), None)
+ self.assertEqual(hosts["owner"], "osv")
+
+ def test_file_status_cmd(self):
+ path = "/file"
+ hosts = self.curl(path + "/etc/hosts?op=GETFILESTATUS")
+ self.assertEqual(hosts["type"], "FILE")
+ self.assert_between("accessTime", 1300000000, 2000000000, hosts["accessTime"])
+ self.assertEqual(hosts["blockSize"], 512)
+ self.assertEqual(hosts["group"], "osv")
+ self.assert_between("length", 20, 40, hosts["length"])
+ self.assert_between("modificationTime", 1300000000, 2000000000, hosts["modificationTime"])
+ self.assertEqual(hosts["owner"], "osv")
+ self.assertEqual(hosts["pathSuffix"], "hosts")
+ self.assertEqual(hosts["permission"], "664")
+ self.assertEqual(hosts["replication"], 1)
+
+ def test_put_file_cmd(self):
+ path = "/file"
+ self.assertHttpError(path + "/etc/hosts?op=COPY&destination="+urllib.parse.quote("/etc/hosts1"), 404, method='PUT')
+ self.assertHttpError(path + "/etc/hosts1?op=RENAME&destination="+urllib.parse.quote("/etc/hosts2"), 404, method='PUT')
+ self.assertHttpError(path + "/etc/hosts2?op=DELETE", 404, method='DELETE')
+
+ @classmethod
+ def setUpClass(cls):
+ cls.file_api = cls.get_json_api("file.json")
diff --git a/modules/httpserver-api/tests/monitoring-api/testfs.py b/modules/httpserver-api/tests/monitoring-api/testfs.py
new file mode 100755
index 00000000..b0924747
--- /dev/null
+++ b/modules/httpserver-api/tests/monitoring-api/testfs.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+import basetest
+
+class testfs(basetest.Basetest):
+ def test_getfs(self):
+ get_path = "/fs/df/"
+ ls = self.curl(get_path)
+ val = ls[0]
+ self.assertEqual(val["mount"], "/")
+ self.assertGreaterEqual(val["ffree"], 200000)
+ self.assertGreaterEqual(val["ftotal"], 200000)
+ self.assertGreaterEqual(val["bfree"], 200000)
+ self.assertGreaterEqual(val["btotal"], 200000)
+ self.assertEqual(val["filesystem"], "/dev/vblk0.1")
+
+ @classmethod
+ def setUpClass(cls):
+ cls.fs_api = cls.get_json_api("fs.json")
diff --git a/modules/httpserver-api/tests/monitoring-api/testnetwork.py b/modules/httpserver-api/tests/monitoring-api/testnetwork.py
new file mode 100755
index 00000000..62b4fc83
--- /dev/null
+++ b/modules/httpserver-api/tests/monitoring-api/testnetwork.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+import time
+import basetest
+
+class testnetwork(basetest.Basetest):
+ def test_ifconfig(self):
+ path = self.path_by_nick(self.network_api, "listIfconfig")
+ intf = self.curl(path + 'lo0')
+ self.assert_key_in("data", intf)
+ self.assert_key_in("config", intf)
+ conf = intf["config"]
+ self.assert_key_in("phys_addr", conf)
+ self.assert_key_in("mask", conf)
+ self.assertEqual(conf["addr"], "127.0.0.1")
+ lst = self.curl(path)
+ self.assertNotEqual(lst, [])
+
+ def test_get_routes(self):
+ path = self.path_by_nick(self.network_api, "getRoute")
+ routes = self.curl(path)
+ route = routes[0]
+ self.assert_key_in("destination", route)
+ self.assert_key_in("gateway", route)
+ self.assert_key_in("flags", route)
+ self.assert_key_in("netif", route)
+ self.assert_key_in("ipv6", route)
+
+ @classmethod
+ def setUpClass(cls):
+ cls.network_api = cls.get_json_api("network.json")
diff --git a/modules/httpserver-api/tests/monitoring-api/testos.py b/modules/httpserver-api/tests/monitoring-api/testos.py
new file mode 100755
index 00000000..73ef6423
--- /dev/null
+++ b/modules/httpserver-api/tests/monitoring-api/testos.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+import time
+import basetest
+
+class testos(basetest.Basetest):
+ def test_os_version(self):
+ path = self.path_by_nick(self.os_api, "os_version")
+ self.assertRegex(self.curl(path), r"v0\.\d+(-rc\d+)?(-\d+-[0-9a-z]+)?" , path)
+
+ def test_vendor(self):
+ self.validate_path(self.os_api, "os_vendor", "Cloudius Systems")
+
+ def test_os_uptime(self):
+ path = self.path_by_nick(self.os_api, "os_uptime")
+ up_time = self.curl(path)
+ time.sleep(2)
+ self.assert_between(path, up_time + 1, up_time + 3, self.curl(path))
+
+ def test_os_date(self):
+ path = self.path_by_nick(self.os_api, "os_date")
+ val = self.curl(path)
+ self.assertRegex(val, "...\\s+...\\s+\\d+\\s+\\d\\d:\\d\\d:\\d\\d\\s+UTC\\s+20..", path)
+
+ def test_os_total_memory(self):
+ path = self.path_by_nick(self.os_api, "os_memory_total")
+ val = self.curl(path)
+ self.assertGreater(val, 1024 * 1024 * 256, msg="Memory should be greater than 256Mb")
+
+ def test_os_free_memory(self):
+ path = self.path_by_nick(self.os_api, "os_memory_free")
+ val = self.curl(path)
+ self.assertGreater(val, 1024 * 1024 * 256, msg="Free memory should be greater than 256Mb")
+
+ def test_os_threads(self):
+ path = self.path_by_nick(self.os_api, "os_threads")
+ val = self.curl(path)
+ self.assert_key_in("time_ms", val)
+ ctime = val["time_ms"]
+ idle_thread = next((item for item in val["list"] if item["name"] == "idle1"), None)
+ idle = idle_thread["cpu_ms"]
+ id = idle_thread["id"]
+ cpu = idle_thread["cpu"]
+ self.assertEqual(cpu,1)
+ time.sleep(2)
+ val = self.curl(path)
+ self.assert_key_in("time_ms", val)
+ self.assert_between(path, ctime + 1000, ctime + 3000, val["time_ms"])
+ idle_thread = next((item for item in val["list"] if item["name"] == "idle1"), None)
+ idle1 = idle_thread["cpu_ms"]
+ self.assert_between(path + " idle thread cputime was" + str(idle)+
+ " new time=" + str(idle1), idle + 1000, idle + 3000, idle1)
+ self.assertEqual(id, idle_thread["id"])
diff --git a/modules/httpserver-api/tests/testhttpserver-monitoring-api.py b/modules/httpserver-api/tests/testhttpserver-monitoring-api.py
new file mode 100755
index 00000000..264536ab
--- /dev/null
+++ b/modules/httpserver-api/tests/testhttpserver-monitoring-api.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+import sys
+import argparse
+import os
+import unittest
+import basetest
+
+from osv import client
+
+parser = argparse.ArgumentParser(description="""Testing the read-only Httpserver""")
+
+module_base = os.path.join(os.path.realpath(os.path.dirname(__file__)), '..')
+osv_base = os.path.join(module_base, '..', '..')
+
+parser.add_argument('--connect', help='Connect to an existing image', action='store_true')
+parser.add_argument('--run_script', help='path to the run image script', default=os.path.join(osv_base, 'scripts', 'run.py'))
+parser.add_argument('--cmd', help='the command to execute')
+parser.add_argument('--use_sudo', help='Use sudo with -n option instead of port forwarding', action='store_true')
+parser.add_argument('--jsondir', help='location of the json files', default=os.path.join(module_base, 'api-doc/listings/'))
+parser.add_argument('--test_image', help='the path to the test image')
+parser.add_argument('--hypervisor', action="store", default="qemu", help="choose hypervisor to run: qemu, firecracker")
+client.Client.add_arguments(parser)
+
+class test_httpserver(basetest.Basetest):
+ @classmethod
+ def setUpClass(cls):
+ basetest.Basetest.start_image()
+
+ @classmethod
+ def tearDownClass(cls):
+ basetest.Basetest.hard_shutdown()
+
+if __name__ == '__main__':
+ basetest.Basetest.set_config(parser)
+ basetest.Basetest.config.check_jvm = False
+ basetest.Basetest.start_image()
+ del sys.argv[1:]
+ api_tests = unittest.TestLoader().discover(os.path.join(module_base, 'tests', 'monitoring-api'), pattern='*.py')
+ test_suite = unittest.TestSuite((api_tests))
+ unittest.TextTestRunner(verbosity=2).run(test_suite)
+ basetest.Basetest.hard_shutdown()
--
2.20.1

Commit Bot

unread,
Mar 27, 2020, 12:00:11 PM3/27/20
to osv...@googlegroups.com, Waldemar Kozaczuk
From: Waldemar Kozaczuk <jwkoz...@gmail.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

httpserver: allow compiling out features not needed for monitoring api

This patch modifies httpserver-api source code to add pre-processor
conditionals - "#if !defined(MONITORING)" - to disable fragments of
code that are not needed for monitoring api. More specifically it disables
all non-GET routes (with exception of trace API) and YAML file-based configuration.
It also disables SSL. All of which is done to allow creating minimal "read-only" version
of httpserver API intended for monitoring purposes only.

Finally it also explicitly makes only certains symbols (like "main") public
by using compiler directives like "__attribute__((visibility("default")))"
and "#pragma GCC visibility push(default)/#pragma GCC visibility pop". The
latter is used to make portion of common code public so that is available to modules
like trace API. Hiding most symbols in monitoring module also helps
to reduce its size.

Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>

---
diff --git a/modules/httpserver-api/api/api.cc b/modules/httpserver-api/api/api.cc
--- a/modules/httpserver-api/api/api.cc
+++ b/modules/httpserver-api/api/api.cc
@@ -240,19 +240,23 @@ class api_param_handler : public handler_base {
routes& _routes;
};

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::api::init(*routes);
}
+#endif

void init(routes& routes)
{
api_json_init_path("Advanced API options");

api_batch.set_handler(new api_param_handler(routes));
+#if !defined(MONITORING)
stop_api.set_handler([](const_req req){
global_server::stop();
return "";
});
+#endif

}

diff --git a/modules/httpserver-api/api/env.cc b/modules/httpserver-api/api/env.cc
--- a/modules/httpserver-api/api/env.cc
+++ b/modules/httpserver-api/api/env.cc
@@ -20,9 +20,11 @@ using namespace json;
using namespace std;
using namespace env_json;

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::env::init(*routes);
}
+#endif

void init(routes& routes)
{
@@ -46,6 +48,7 @@ void init(routes& routes)
return res;
});

+#if !defined(MONITORING)
setEnv.set_handler([](const_req req) {
string param = req.param.at("var").substr(1);
if (setenv(param.c_str(),
@@ -62,7 +65,7 @@ void init(routes& routes)
}
return "";
});
-
+#endif
}

}
diff --git a/modules/httpserver-api/api/file.cc b/modules/httpserver-api/api/file.cc
--- a/modules/httpserver-api/api/fs.cc
+++ b/modules/httpserver-api/api/fs.cc
@@ -33,9 +33,11 @@ static void fill_dfstat(DFStat& dfstat, const osv::mount_desc& mount, const stru
dfstat.blocksize = st.f_frsize;
}

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::fs::init(*routes);
}
+#endif

void init(routes& routes) {

diff --git a/modules/httpserver-api/api/hardware.cc b/modules/httpserver-api/api/hardware.cc
--- a/modules/httpserver-api/api/hardware.cc
+++ b/modules/httpserver-api/api/hardware.cc
@@ -24,9 +24,11 @@ using namespace std;
using namespace json;
using namespace hardware_json;

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::hardware::init(*routes);
}
+#endif

void init(routes& routes)
{
diff --git a/modules/httpserver-api/api/network.cc b/modules/httpserver-api/api/network.cc
--- a/modules/httpserver-api/api/network.cc
+++ b/modules/httpserver-api/api/network.cc
@@ -30,7 +30,6 @@ static Interface get_interface(const string& name, ifnet* ifp, long time)
Interface f;
interface intf(name);

-
if_data cur_data = { 0 };
if (!set_interface_info(ifp, cur_data, intf)) {
throw server_error_exception("Failed getting interface information");
@@ -43,9 +42,11 @@ static Interface get_interface(const string& name, ifnet* ifp, long time)
return f;
}

+#if !defined(MONITORING)
extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::network::init(*routes);
}
+#endif

/**
* Initialize the routes object with specific routes mapping
diff --git a/modules/httpserver-api/api/os.cc b/modules/httpserver-api/api/os.cc
--- a/modules/httpserver-api/api/trace.cc
+++ b/modules/httpserver-api/api/trace.cc
@@ -25,7 +25,7 @@ using namespace httpserver::json::trace_json;
static std::unordered_map<tracepoint_base*,
std::unique_ptr<tracepoint_counter>> counters;

-extern "C" void httpserver_plugin_register_routes(httpserver::routes* routes) {
+extern "C" void __attribute__((visibility("default"))) httpserver_plugin_register_routes(httpserver::routes* routes) {
httpserver::api::trace::init(*routes);
}

diff --git a/modules/httpserver-api/global_server.cc b/modules/httpserver-api/global_server.cc
--- a/modules/httpserver-api/global_server.hh
+++ b/modules/httpserver-api/global_server.hh
@@ -12,7 +12,9 @@
#include <vector>
#include <mutex>
#include <condition_variable>
+#if !defined(MONITORING)
#include <yaml-cpp/node/iterator.h>
+#endif

namespace httpserver {
/**
@@ -64,8 +66,10 @@ private:

global_server();
void set_routes();
+#if !defined(MONITORING)
void setup_redirects(const YAML::Node& node);
void setup_file_mappings(const YAML::Node& node);
+#endif
void load_plugin(const std::string& path);
static global_server* instance;
routes _routes;
diff --git a/modules/httpserver-api/handlers.cc b/modules/httpserver-api/handlers.cc
--- a/modules/httpserver-api/json/api_docs.cc
+++ b/modules/httpserver-api/json/api_docs.cc
@@ -43,9 +43,11 @@ class api_registry : public handler_base {
const string api_registry::base_path = "/api-doc";
static api_registry* registry = nullptr;

+#pragma GCC visibility push(default)
void register_api(const std::string& api, const std::string& description) {
registry->reg(api, description);
}
+#pragma GCC visibility pop

void api_doc_init(routes& _routes) {
registry = new api_registry(_routes);
diff --git a/modules/httpserver-api/json/api_docs.hh b/modules/httpserver-api/json/api_docs.hh
--- a/modules/httpserver-api/json/api_docs.hh
+++ b/modules/httpserver-api/json/api_docs.hh
@@ -87,8 +87,10 @@ struct api_docs : public json::json_base {
}
};

+#pragma GCC visibility push(default)
void register_api(const std::string& api,
const std::string& description);
+#pragma GCC visibility pop

/**
* Initialize the routes object with specific routes mapping
diff --git a/modules/httpserver-api/json/formatter.cc b/modules/httpserver-api/json/formatter.cc
--- a/modules/httpserver-api/json/formatter.cc
+++ b/modules/httpserver-api/json/formatter.cc
@@ -13,6 +13,7 @@

using namespace std;

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -97,3 +98,4 @@ std::string formatter::json_escape_UTF8_string(const std::string& utf8_string) {

}
}
+#pragma GCC visibility pop
diff --git a/modules/httpserver-api/json/formatter.hh b/modules/httpserver-api/json/formatter.hh
--- a/modules/httpserver-api/json/formatter.hh
+++ b/modules/httpserver-api/json/formatter.hh
@@ -12,6 +12,7 @@
#include <time.h>
#include <sstream>

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -124,4 +125,5 @@ private:

}
}
+#pragma GCC visibility pop
#endif /* FORMATTER_HH_ */
diff --git a/modules/httpserver-api/json/json_elements.cc b/modules/httpserver-api/json/json_elements.cc
--- a/modules/httpserver-api/json/json_elements.cc
+++ b/modules/httpserver-api/json/json_elements.cc
@@ -13,6 +13,7 @@

using namespace std;

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -107,3 +108,4 @@ bool json_base::is_verify() const

}
}
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/json/json_elements.hh b/modules/httpserver-api/json/json_elements.hh
--- a/modules/httpserver-api/json/json_elements.hh
+++ b/modules/httpserver-api/json/json_elements.hh
@@ -14,6 +14,7 @@
#include <sstream>
#include "formatter.hh"

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -224,4 +225,5 @@ struct json_return_type {
}

}
+#pragma GCC visibility pop
#endif /* JSON_ELEMENTS_HH_ */
diff --git a/modules/httpserver-api/json/json_path.cc b/modules/httpserver-api/json/json_path.cc
--- a/modules/httpserver-api/json/json_path.cc
+++ b/modules/httpserver-api/json/json_path.cc
@@ -7,6 +7,7 @@

#include "json_path.hh"

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -36,3 +37,4 @@ path_description* path_description::get(string nickname)
}

}
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/json/json_path.hh b/modules/httpserver-api/json/json_path.hh
--- a/modules/httpserver-api/json/json_path.hh
+++ b/modules/httpserver-api/json/json_path.hh
@@ -14,6 +14,7 @@
#include <tuple>
#include "common.hh"

+#pragma GCC visibility push(default)
namespace httpserver {

namespace json {
@@ -142,4 +143,5 @@ struct path_description {

}
}
+#pragma GCC visibility pop
#endif /* JSON_PATH_HH_ */
diff --git a/modules/httpserver-api/main.cc b/modules/httpserver-api/main.cc
--- a/modules/httpserver-api/main.cc
+++ b/modules/httpserver-api/main.cc
@@ -40,7 +40,7 @@ static void handle_parse_error(const std::string &message)
exit(1);
}

-int main(int argc, char* argv[])
+int __attribute__((visibility("default"))) main(int argc, char* argv[])
{
auto options_values = options::parse_options_values(argc - 1, argv + 1, handle_parse_error);

diff --git a/modules/httpserver-api/path_holder.cc b/modules/httpserver-api/path_holder.cc
--- a/modules/httpserver-api/path_holder.cc
+++ b/modules/httpserver-api/path_holder.cc
@@ -7,6 +7,7 @@
#include "path_holder.hh"
#include <assert.h>

+#pragma GCC visibility push(default)
namespace httpserver {

routes* path_holder::_routes = nullptr;
@@ -36,3 +37,4 @@ handler_base& path_holder::set_handler(const json_request_function& fun) const
}

}
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/path_holder.hh b/modules/httpserver-api/path_holder.hh
--- a/modules/httpserver-api/path_holder.hh
+++ b/modules/httpserver-api/path_holder.hh
@@ -11,6 +11,7 @@
#include "handlers.hh"
#include "routes.hh"

+#pragma GCC visibility push(default)
namespace httpserver {
/**
* Path holder glues handlers and implementation.
@@ -89,5 +90,5 @@ private:
};

}
-
+#pragma GCC visibility pop
#endif /* PATH_HOLDER_HH_ */
diff --git a/modules/httpserver-api/reply.cc b/modules/httpserver-api/reply.cc
--- a/modules/httpserver-api/reply.cc
+++ b/modules/httpserver-api/reply.cc
@@ -11,6 +11,7 @@
#include "reply.hh"
#include <string>

+#pragma GCC visibility push(default)
namespace http {

namespace server {
@@ -239,3 +240,4 @@ reply& reply::add_header(const std::string& h, const std::string& value) {
} // namespace server

} // namespace http
+#pragma GCC visibility pop
\ No newline at end of file
diff --git a/modules/httpserver-api/reply.hh b/modules/httpserver-api/reply.hh
--- a/modules/httpserver-api/reply.hh
+++ b/modules/httpserver-api/reply.hh
@@ -17,6 +17,7 @@
#include <vector>
#include <boost/asio.hpp>

+#pragma GCC visibility push(default)
namespace http {

namespace server {
@@ -87,4 +88,5 @@ struct reply {

} // namespace http

+#pragma GCC visibility pop
#endif // HTTP_REPLY_HPP
diff --git a/modules/httpserver-api/server.cc b/modules/httpserver-api/server.cc

Commit Bot

unread,
Mar 27, 2020, 12:00:14 PM3/27/20
to osv...@googlegroups.com, Waldemar Kozaczuk
From: Waldemar Kozaczuk <jwkoz...@gmail.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

httpserver: add monitoring API unit tests

Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>

---
diff --git a/modules/httpserver-api/tests/basetest.py b/modules/httpserver-api/tests/basetest.py
--- a/modules/httpserver-api/tests/basetest.py
+++ b/modules/httpserver-api/tests/basetest.py
@@ -85,9 +85,9 @@ def validate_path_regex(self, api_definition, nickname, expr):
path = self.path_by_nick(api_definition, nickname)
self.assertRegex(self.curl(path), expr)

- def assertHttpError(self, url, code=404):
+ def assertHttpError(self, url, code=404, method='GET', data=None):
try:
- self.curl(url)
+ self.curl(url, method, data)
except HttpError as e:
if e.code != code:
raise Exception('Expected error code %d but got %d' % (code, e.code))
@@ -159,6 +159,13 @@ def shutdown(cls):
raise Exception("Fail to shutdown server")
time.sleep(1)

+ @classmethod
+ def hard_shutdown(cls):
+ child_pid = subprocess.call(['pgrep', "-P", str(cls.os_process.pid)])
+ subprocess.call(['kill', '-9', str(child_pid)])
+ cls.os_process.kill()
+ cls.os_process.wait()
+
@classmethod
def start_image(cls):
if cls.config.check_jvm:
diff --git a/modules/httpserver-api/tests/monitoring-api/testenv.py b/modules/httpserver-api/tests/monitoring-api/testenv.py
--- a/modules/httpserver-api/tests/monitoring-api/testenv.py
+++ b/modules/httpserver-api/tests/monitoring-api/testenv.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+import basetest
+
+class testenv(basetest.Basetest):
+ def test_env(self):
+ param = "test-param"
+ get_path = self.path_by_nick(self.env_api, "list_env") + param
+ set_path = get_path + "?val=TEST"
+ self.assertHttpError(set_path, 404, method='POST')
+
+ lst = self.curl(self.path_by_nick(self.env_api, "list_env"))
+ if not "OSV_CPUS=4" in lst:
+ raise Exception('environment variable OSV_CPUS not found in list')
+
+ self.assertHttpError(get_path, 404, method="DELETE")
+
+ @classmethod
+ def setUpClass(cls):
+ cls.env_api = cls.get_json_api("env.json")
diff --git a/modules/httpserver-api/tests/monitoring-api/testfile.py b/modules/httpserver-api/tests/monitoring-api/testfile.py
--- a/modules/httpserver-api/tests/monitoring-api/testfile.py
--- a/modules/httpserver-api/tests/monitoring-api/testfs.py
+++ b/modules/httpserver-api/tests/monitoring-api/testfs.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+import basetest
+
+class testfs(basetest.Basetest):
+ def test_getfs(self):
+ get_path = "/fs/df/"
+ ls = self.curl(get_path)
+ val = ls[0]
+ self.assertEqual(val["mount"], "/")
+ self.assertGreaterEqual(val["ffree"], 200000)
+ self.assertGreaterEqual(val["ftotal"], 200000)
+ self.assertGreaterEqual(val["bfree"], 200000)
+ self.assertGreaterEqual(val["btotal"], 200000)
+ self.assertEqual(val["filesystem"], "/dev/vblk0.1")
+
+ @classmethod
+ def setUpClass(cls):
+ cls.fs_api = cls.get_json_api("fs.json")
diff --git a/modules/httpserver-api/tests/monitoring-api/testnetwork.py b/modules/httpserver-api/tests/monitoring-api/testnetwork.py
--- a/modules/httpserver-api/tests/monitoring-api/testnetwork.py
--- a/modules/httpserver-api/tests/monitoring-api/testos.py
--- a/modules/httpserver-api/tests/testhttpserver-monitoring-api.py

Commit Bot

unread,
Mar 27, 2020, 12:00:15 PM3/27/20
to osv...@googlegroups.com, Waldemar Kozaczuk
From: Waldemar Kozaczuk <jwkoz...@gmail.com>
Committer: Waldemar Kozaczuk <jwkoz...@gmail.com>
Branch: master

httpserver: add read-only monitoring module

This patch adds new module httpserver-monitoring-api that provides
read-only subset of the API intended for monitoring purposes only.
The monitoring flavor of httpserver provides following routes:

- /api
- /env
- /file
- /fs
- /hardware
- /network
- /os

where each of the routes expose GET-only paths.
The monitoring module disables YAML file based configuration
as well as SSL connectivity.

It is also possible to add trace plugin - libhttpserver-api_trace.so.

Please note that monitoring module re-uses the same httpserver-api
source code however when compiling we pass '-DMONITORING' flag
to disable subset of code not needed for monitoring functionality.
We also compile the code with '-fvisibility=hidden' to further reduce
size of the monitoring executable (<800K).

Fixes #820

Signed-off-by: Waldemar Kozaczuk <jwkoz...@gmail.com>

---
diff --git a/modules/httpserver-monitoring-api/.gitignore b/modules/httpserver-monitoring-api/.gitignore
--- a/modules/httpserver-monitoring-api/.gitignore
+++ b/modules/httpserver-monitoring-api/.gitignore
@@ -0,0 +1,4 @@
+*.so
+autogen/*
+obj/*
+*.manifest
diff --git a/modules/httpserver-monitoring-api/Makefile b/modules/httpserver-monitoring-api/Makefile
--- a/modules/httpserver-monitoring-api/Makefile
--- a/modules/httpserver-monitoring-api/api-doc/listings/api.json
--- a/modules/httpserver-monitoring-api/api-doc/listings/env.json
--- a/modules/httpserver-monitoring-api/api-doc/listings/file.json
--- a/modules/httpserver-monitoring-api/api-doc/listings/fs.json
--- a/modules/httpserver-monitoring-api/api-doc/listings/hardware.json
--- a/modules/httpserver-monitoring-api/api-doc/listings/network.json
--- a/modules/httpserver-monitoring-api/api-doc/listings/os.json
--- a/modules/httpserver-monitoring-api/module.py
--- a/modules/httpserver-monitoring-api/mpm/package.yaml
Reply all
Reply to author
Forward
0 new messages