Commit b4035c1f authored by Micha Müller's avatar Micha Müller
Browse files

Merge branch 'CommonRestServer' into 'master'

Common rest server

See merge request !3
parents 72b1ca02 9f44fe66
......@@ -6,7 +6,7 @@ install/
*.o
*.so
*.dylib
collectagent
collectagent/collectagent
dcdbpusher/dcdbpusher
tools/dcdbconfig/dcdbconfig
tools/dcdbcsvimport/dcdbcsvimport
......
......@@ -4,30 +4,38 @@ MAKEFILENAME := $(lastword $(MAKEFILE_LIST))
SUB_DIRS = lib collectagent dcdbpusher analytics tools scripts
SOURCEFORGE_MROR = vorboss
CASSANDRA_VERSION = 3.0.18
MOSQUITTO_VERSION = 1.5.5
BOOST_VERSION = 1.58.0
BOOST_VERSION = 1.70.0
OPENSSL_VERSION = 1.0.2l
CPPDRV_VERSION = 2.10.0
LIBUV_VERSION = 1.24.0
SOURCEFORGE_MROR = vorboss
CPPNET_VERSION = 0.12.0-final
BACNET-STACK_VERSION = 0.8.5
FREEIPMI_VERSION = 1.5.5
NET-SNMP_VERSION = 5.8
BOOST_VERSION_U = $(subst .,_,$(BOOST_VERSION))
DISTFILES = apache-cassandra-$(CASSANDRA_VERSION).tar.gz;http://archive.apache.org/dist/cassandra/$(CASSANDRA_VERSION)/apache-cassandra-$(CASSANDRA_VERSION)-bin.tar.gz \
mosquitto-$(MOSQUITTO_VERSION).tar.gz;http://mosquitto.org/files/source/mosquitto-$(MOSQUITTO_VERSION).tar.gz \
boost_$(BOOST_VERSION_U).tar.gz;http://$(SOURCEFORGE_MROR).dl.sourceforge.net/project/boost/boost/$(BOOST_VERSION)/boost_$(BOOST_VERSION_U).tar.gz \
openssl-$(OPENSSL_VERSION).tar.gz;https://www.openssl.org/source/openssl-$(OPENSSL_VERSION).tar.gz \
libuv-v$(LIBUV_VERSION).tar.gz;https://dist.libuv.org/dist/v$(LIBUV_VERSION)/libuv-v$(LIBUV_VERSION).tar.gz \
cpp-driver-$(CPPDRV_VERSION).tar.gz;https://github.com/datastax/cpp-driver/archive/$(CPPDRV_VERSION).tar.gz \
cpp-netlib-$(CPPNET_VERSION).tar.gz;http://downloads.cpp-netlib.org/0.12.0/cpp-netlib-$(CPPNET_VERSION).tar.gz \
bacnet-stack-$(BACNET-STACK_VERSION).tgz;https://downloads.sourceforge.net/project/bacnet/bacnet-stack/bacnet-stack-$(BACNET-STACK_VERSION)/bacnet-stack-$(BACNET-STACK_VERSION).tgz \
freeipmi-$(FREEIPMI_VERSION).tar.gz;http://ftp.gnu.org/gnu/freeipmi/freeipmi-$(FREEIPMI_VERSION).tar.gz \
net-snmp-$(NET-SNMP_VERSION).tar.gz;https://sourceforge.net/projects/net-snmp/files/net-snmp/$(NET-SNMP_VERSION)/net-snmp-$(NET-SNMP_VERSION).tar.gz/download
DISTFILES_HASHES = apache-cassandra-3.0.18.tar.gz|94dbdaa58b366166c53f881b8e266bc8;mosquitto-1.5.5.tar.gz|a17dffc6f63b2a4ab2eb5c51139e60e9;boost_1_58_0.tar.gz|5a5d5614d9a07672e1ab2a250b5defc5;openssl-1.0.2l.tar.gz|f85123cd390e864dfbe517e7616e6566;cpp-driver-2.10.0.tar.gz|6d15dd2cccd2efd1fdc86089d26971d0;libuv-v1.24.0.tar.gz|90320330757253b07404d2a97f59c66b;cpp-netlib-0.12.0-final.tar.gz|29b87c0e8c1a9e7fbdea5afcec947d53;bacnet-stack-$(BACNET-STACK_VERSION).tgz|66b69111d91432fa67a7c6c1a653434d;freeipmi-$(FREEIPMI_VERSION).tar.gz|b8abfefee0b757f351d8fab777e3c1bb;net-snmp-$(NET-SNMP_VERSION).tar.gz|63bfc65fbb86cdb616598df1aff6458a
freeipmi-$(FREEIPMI_VERSION).tar.gz;http://ftp.gnu.org/gnu/freeipmi/freeipmi-$(FREEIPMI_VERSION).tar.gz \
net-snmp-$(NET-SNMP_VERSION).tar.gz;https://sourceforge.net/projects/net-snmp/files/net-snmp/$(NET-SNMP_VERSION)/net-snmp-$(NET-SNMP_VERSION).tar.gz/download
DISTFILES_HASHES = apache-cassandra-3.0.18.tar.gz|94dbdaa58b366166c53f881b8e266bc8;\
mosquitto-1.5.5.tar.gz|a17dffc6f63b2a4ab2eb5c51139e60e9;\
boost_1_70_0.tar.gz|fea771fe8176828fabf9c09242ee8c26;\
openssl-1.0.2l.tar.gz|f85123cd390e864dfbe517e7616e6566;\
cpp-driver-2.10.0.tar.gz|6d15dd2cccd2efd1fdc86089d26971d0;\
libuv-v1.24.0.tar.gz|90320330757253b07404d2a97f59c66b;\
bacnet-stack-0.8.5.tgz|66b69111d91432fa67a7c6c1a653434d;\
freeipmi-1.5.5.tar.gz|b8abfefee0b757f351d8fab777e3c1bb;\
net-snmp-5.8.tar.gz|63bfc65fbb86cdb616598df1aff6458a
include common.mk
......@@ -149,7 +157,7 @@ $(DCDBDEPSPATH)/boost_$(BOOST_VERSION_U)/.built: $(DCDBDEPSPATH)/boost_$(BOOST_V
$(DCDBDEPSPATH)/boost_$(BOOST_VERSION_U)/.installed: $(DCDBDEPSPATH)/boost_$(BOOST_VERSION_U)/.built
cd $(@D) && ./b2 -j $(MAKETHREADS) install && touch $(@)
cd $(@D) && ./b2 -j $(MAKETHREADS) --no-cmake-config install && touch $(@)
$(DCDBDEPSPATH)/libuv-v$(LIBUV_VERSION)/.built: $(DCDBDEPSPATH)/libuv-v$(LIBUV_VERSION)/.patched
@echo "Building libuv..."
......@@ -184,24 +192,6 @@ $(DCDBDEPSPATH)/cpp-driver-$(CPPDRV_VERSION)/.installed: $(DCDBDEPSPATH)/cpp-dri
@echo "Installing cpp-driver..."
cd $(@D)/build && make install && touch $(@)
$(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.built: $(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.patched
@echo "Building cpp-netlib..."
mkdir -p $(DCDBDEPSPATH)/cpp-netlib_build
cd $(DCDBDEPSPATH)/cpp-netlib_build && \
CC=$(FULL_CC) CXX=$(FULL_CXX) cmake $(CMAKE_CROSS_FLAGS) \
-DCPP-NETLIB_ENABLE_HTTPS=off \
-DCPP-NETLIB_BUILD_TESTS=OFF \
-DCPP-NETLIB_BUILD_EXAMPLES=OFF \
-DCMAKE_INSTALL_LIBDIR=lib \
-DCMAKE_INSTALL_PREFIX=$(DCDBDEPLOYPATH)/ \
-DBOOST_ROOT=$(DCDBDEPSPATH)/boost_$(BOOST_VERSION_U)/ \
$(@D) && \
make -j $(MAKETHREADS) && touch $(@)
$(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.installed: $(DCDBDEPSPATH)/cpp-netlib-$(CPPNET_VERSION)/.built | $(DCDBDEPLOYPATH)
@echo "Installing cpp-netlib..."
cd $(DCDBDEPSPATH)/cpp-netlib_build && make install && touch $(@)
$(DCDBDEPSPATH)/apache-cassandra-$(CASSANDRA_VERSION)/.built: $(DCDBDEPSPATH)/apache-cassandra-$(CASSANDRA_VERSION)/.patched
@touch $(DCDBDEPSPATH)/apache-cassandra-$(CASSANDRA_VERSION)/.built
......
......@@ -262,239 +262,389 @@ bool AnalyticsManager::stop(const string& plugin, const string& analyzer) {
return out;
}
restResponse_t AnalyticsManager::REST(const vector<string>& pathStrs, const vector<pair<string,string>>& queries, const string& method, boost::asio::io_service& io) {
// Some preliminary checks
if(_status != LOADED)
throw runtime_error("Cannot forward REST command, AnalyticsManager is not loaded!");
if (method != "GET" && method != "PUT")
throw invalid_argument("Unsupported REST method!");
if(pathStrs.size() < 2 || pathStrs[0] != "analytics")
throw invalid_argument("Received malformed request!");
// Determining if JSON output was requested
bool json = false;
for (auto& p : queries)
if (p.first == "json")
json = stoi(p.second) > 0;
restResponse_t reply;
/******************************************************************************/
/* Rest API endpoint methods */
/******************************************************************************/
#define stdBind(fun) std::bind(&AnalyticsManager::fun, \
this, \
std::placeholders::_1, \
std::placeholders::_2)
void AnalyticsManager::addRestEndpoints(RESTHttpsServer* restServer) {
restServer->addEndpoint("/analytics/help", {http::verb::get, stdBind(GET_analytics_help)});
restServer->addEndpoint("/analytics/plugins", {http::verb::get, stdBind(GET_analytics_plugins)});
restServer->addEndpoint("/analytics/sensors", {http::verb::get, stdBind(GET_analytics_sensors)});
restServer->addEndpoint("/analytics/units", {http::verb::get, stdBind(GET_analytics_units)});
restServer->addEndpoint("/analytics/analyzers", {http::verb::get, stdBind(GET_analytics_analyzers)});
restServer->addEndpoint("/analytics/start", {http::verb::put, stdBind(PUT_analytics_start)});
restServer->addEndpoint("/analytics/stop", {http::verb::put, stdBind(PUT_analytics_stop)});
restServer->addEndpoint("/analytics/reload", {http::verb::put, stdBind(PUT_analytics_reload)});
restServer->addEndpoint("/analytics/compute", {http::verb::put, stdBind(PUT_analytics_compute)});
restServer->addEndpoint("/analytics/analyzer", {http::verb::put, stdBind(PUT_analytics_analyzer)});
}
void AnalyticsManager::GET_analytics_help(endpointArgs){
if (!managerLoaded(res)) {
return;
}
res.body() = restCheatSheet;
res.result(http::status::ok);
}
void AnalyticsManager::GET_analytics_plugins(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
std::ostringstream data;
if (getQuery("json", queries) == "true") {
boost::property_tree::ptree root, plugins;
for(const auto& p : _plugins) {
plugins.put(p.id, "");
}
root.add_child("plugins", plugins);
boost::property_tree::write_json(data, root, true);
} else {
for(const auto& p : _plugins) {
data << p.id << "\n";
}
}
res.body() = data.str();
res.result(http::status::ok);
}
// GET block of commands
if (method == "GET") {
// Help cheatsheet command
if (pathStrs[1] == "help") {
reply.response = restCheatSheet;
// Command to list data analytics plugins
} else if (pathStrs[1] == "plugins") {
if (json) {
boost::property_tree::ptree root, plugins;
for(auto& p : _plugins)
plugins.put(p.id, "");
root.add_child("plugins", plugins);
boost::property_tree::write_json(data, root, true);
} else
for(auto& p : _plugins)
data << p.id << "\n";
reply.data = data.str();
// Managing commands that have a path length greater than 2
} else {
if (pathStrs.size() < 3)
throw invalid_argument("Received malformed request, no second path part!");
string analyzer = pathStrs.size() > 3 ? pathStrs[2] : "";
string action = pathStrs[pathStrs.size() - 1];
string plugin = pathStrs[1];
// Listing all sensors in one or all analyzers of a plugin; the [analyzer] block is optional
if (action == "sensors") {
bool found = false;
for (auto &p : _plugins) {
if (p.id == plugin) {
if (json) {
boost::property_tree::ptree root, sensors;
// In JSON mode, sensors are arranged hierarchically by plugin->analyzer->sensor
for (auto &a : p.configurator->getAnalyzers())
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
boost::property_tree::ptree group;
for (auto &u : a->getUnits())
for (auto &s : u->getBaseOutputs())
// Explicitly adding nodes to the ptree as to prevent BOOST from performing
// parsing on the node names
group.push_back(boost::property_tree::ptree::value_type(s->getName(), boost::property_tree::ptree(s->getMqtt())));
sensors.add_child(a->getName(), group);
}
root.add_child(p.id, sensors);
boost::property_tree::write_json(data, root, true);
} else {
for (auto &a : p.configurator->getAnalyzers())
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
for (auto &u : a->getUnits())
for (auto &s : u->getBaseOutputs())
data << a->getName() << "." << s->getName() << " " << s->getMqtt() << "\n";
}
void AnalyticsManager::GET_analytics_sensors(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
const std::string plugin = getQuery("plugin", queries);
const std::string analyzer = getQuery("analyzer", queries);
if (!hasPlugin(plugin, res)) {
return;
}
bool found = false;
std::ostringstream data;
for (const auto& p : _plugins) {
if (p.id == plugin) {
if (getQuery("json", queries) == "true") {
boost::property_tree::ptree root, sensors;
// In JSON mode, sensors are arranged hierarchically by plugin->analyzer->sensor
for (const auto& a : p.configurator->getAnalyzers()) {
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
boost::property_tree::ptree group;
for (const auto& u : a->getUnits()) {
for (const auto& s : u->getBaseOutputs()) {
// Explicitly adding nodes to the ptree as to prevent BOOST from performing
// parsing on the node names
group.push_back(boost::property_tree::ptree::value_type(s->getName(), boost::property_tree::ptree(s->getMqtt())));
}
}
reply.data = data.str();
break;
sensors.add_child(a->getName(), group);
}
}
if (!found)
throw domain_error("Plugin or analyzer not found!");
} else if (action == "units") {
bool found = false;
for (auto &p : _plugins) {
if (p.id == plugin) {
if (json) {
boost::property_tree::ptree root, units;
// In JSON mode, units are arranged hierarchically by plugin->analyzer->unit
for (auto &a : p.configurator->getAnalyzers())
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
boost::property_tree::ptree group;
for (auto &u : a->getUnits())
group.push_back(boost::property_tree::ptree::value_type(u->getName(), boost::property_tree::ptree()));
units.add_child(a->getName(), group);
}
root.add_child(p.id, units);
boost::property_tree::write_json(data, root, true);
} else {
for (auto &a : p.configurator->getAnalyzers())
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
for (auto &u : a->getUnits())
data << a->getName() << "." << u->getName() << "\n";
}
root.add_child(p.id, sensors);
boost::property_tree::write_json(data, root, true);
} else {
for (const auto& a : p.configurator->getAnalyzers()) {
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
for (const auto& u : a->getUnits()) {
for (const auto& s : u->getBaseOutputs()) {
data << a->getName() << "." << s->getName() << " " << s->getMqtt() << "\n";
}
}
reply.data = data.str();
break;
}
}
if (!found)
throw domain_error("Plugin or analyzer not found!");
} else if (action == "analyzers") {
if(analyzer != "")
throw invalid_argument("Analyzers GET command does not support analyzer names!");
bool found = false;
for (auto &p : _plugins) {
if (p.id == plugin) {
if (json) {
boost::property_tree::ptree root, analyzers;
// For each analyzer, we output its type as well
for (auto &a : p.configurator->getAnalyzers())
analyzers.push_back(boost::property_tree::ptree::value_type(a->getName(), boost::property_tree::ptree(a->getStreaming() ? "streaming" : "on-demand")));
root.add_child(p.id, analyzers);
boost::property_tree::write_json(data, root, true);
} else {
for (auto &a : p.configurator->getAnalyzers())
data << a->getName() << " " << (a->getStreaming() ? "streaming\n" : "on-demand\n");
}
res.body() = data.str();
res.result(http::status::ok);
break;
}
}
if (!found) {
res.body() = "Plugin or analyzer not found!\n";
res.result(http::status::not_found);
}
}
void AnalyticsManager::GET_analytics_units(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
const std::string plugin = getQuery("plugin", queries);
const std::string analyzer = getQuery("analyzer", queries);
if (!hasPlugin(plugin, res)) {
return;
}
bool found = false;
std::ostringstream data;
for (const auto& p : _plugins) {
if (p.id == plugin) {
if (getQuery("json", queries) == "true") {
boost::property_tree::ptree root, units;
// In JSON mode, sensors are arranged hierarchically by plugin->analyzer->sensor
for (const auto& a : p.configurator->getAnalyzers())
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
boost::property_tree::ptree group;
for (const auto& u : a->getUnits()) {
group.push_back(boost::property_tree::ptree::value_type(u->getName(), boost::property_tree::ptree()));
}
units.add_child(a->getName(), group);
}
root.add_child(p.id, units);
boost::property_tree::write_json(data, root, true);
} else {
for (const auto& a : p.configurator->getAnalyzers()) {
if (a->getStreaming() && (analyzer == "" || analyzer == a->getName())) {
found = true;
reply.data = data.str();
break;
for (const auto& u : a->getUnits()) {
data << a->getName() << "." << u->getName() << "\n";
}
}
}
if (!found)
throw domain_error("Plugin not found!");
} else
throw invalid_argument("Unknown action " + action + " requested");
}
res.body() = data.str();
res.result(http::status::ok);
break;
}
// PUT block of commands
} else if (method == "PUT") {
}
if (!found) {
res.body() = "Plugin or analyzer not found!\n";
res.result(http::status::not_found);
}
}
void AnalyticsManager::GET_analytics_analyzers(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
if (pathStrs.size() < 3)
throw invalid_argument("Received malformed request, no second path part!");
const std::string plugin = getQuery("plugin", queries);
string analyzer = pathStrs.size() > 3 ? pathStrs[2] : "";
string action = pathStrs[pathStrs.size() - 1];
string plugin = pathStrs[1];
if (!hasPlugin(plugin, res)) {
return;
}
// Managing generic plugin PUT actions
if (action == "start") {
if( start(plugin, analyzer) ) {
reply.response = "Plugin " + plugin + " " + analyzer + ": Sensors started";
} else
throw domain_error("Plugin or analyzer not found!");
} else if (action == "stop") {
if( stop(plugin, analyzer) ) {
reply.response = "Plugin " + plugin + " " + analyzer + ": Sensors stopped";
} else
throw domain_error("Plugin or analyzer not found!");
} else if (action == "reload") {
if(!reload(io, plugin))
throw domain_error("Plugin not found or reload failed, please check the config files and MQTT topics!");
else if(!start(plugin))
throw runtime_error("Plugin cannot be restarted!");
reply.response = "Plugin " + plugin + ": Sensors reloaded";
} else if (action == "compute") {
if(pathStrs.size() < 4)
throw invalid_argument("Received malformed request, no third path part!");
string unit = SensorNavigator::rootKey;
for (auto& p : queries)
if (p.first == "unit")
unit = p.second;
bool found=false, unitFound=false;
for (auto &p : _plugins)
if (p.id == plugin)
for (auto &a : p.configurator->getAnalyzers())
if( a->getName() == analyzer ) {
found = true;
map <string, reading_t> outMap;
try {
outMap = a->computeOnDemand(unit);
unitFound = true;
} catch(const domain_error& e) {
// In the particular case where an analyzer is duplicated, it could be that the right
// unit is found only after a few tries. Therefore, we handle the domain_error
// exception raised in AnalyzerTemplate, and allow the search to continue
if(a->getStreaming() && a->getDuplicate())
continue;
else
throw;
}
if (json) {
boost::property_tree::ptree root, outputs;
// Iterating through the outputs of the on-demand computation and adding them to a JSON
for (const auto& kv : outMap) {
boost::property_tree::ptree sensor;
sensor.push_back(boost::property_tree::ptree::value_type("timestamp", boost::property_tree::ptree(to_string(kv.second.timestamp))));
sensor.push_back(boost::property_tree::ptree::value_type("value", boost::property_tree::ptree(to_string(kv.second.value))));
outputs.push_back(boost::property_tree::ptree::value_type(kv.first, sensor));
}
root.add_child(a->getName(), outputs);
boost::property_tree::write_json(data, root, true);
} else {
for (const auto& kv : outMap)
data << kv.first << " ts: " << kv.second.timestamp << " v: " << kv.second.value << "\n";
}
reply.data = data.str();
break;
std::ostringstream data;
for (const auto& p : _plugins) {
if (p.id == plugin) {
if (getQuery("json", queries) == "true") {
boost::property_tree::ptree root, analyzers;
// For each analyzer, we output its type as well
for (const auto& a : p.configurator->getAnalyzers()) {
analyzers.push_back(boost::property_tree::ptree::value_type(a->getName(), boost::property_tree::ptree(a->getStreaming() ? "streaming" : "on-demand")));
}
root.add_child(p.id, analyzers);
boost::property_tree::write_json(data, root, true);
} else {
for (const auto& a : p.configurator->getAnalyzers()) {
data << a->getName() << " " << (a->getStreaming() ? "streaming\n" : "on-demand\n");
}
}
res.body() = data.str();
res.result(http::status::ok);
return;
}
}
}
void AnalyticsManager::PUT_analytics_start(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
const std::string plugin = getQuery("plugin", queries);
const std::string analyzer = getQuery("analyzer", queries);
if (start(plugin, analyzer)) {
res.body() = "Plugin " + plugin + " " + analyzer + ": Sensors started!\n";
res.result(http::status::ok);
} else {
res.body() = "Plugin or analyzer not found!\n";
res.result(http::status::not_found);
}
}
void AnalyticsManager::PUT_analytics_stop(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
const std::string plugin = getQuery("plugin", queries);
const std::string analyzer = getQuery("analyzer", queries);
if (stop(plugin, analyzer)) {
res.body() = "Plugin " + plugin + " " + analyzer + ": Sensors stopped!\n";
res.result(http::status::ok);
} else {
res.body() = "Plugin or analyzer not found!\n";
res.result(http::status::not_found);
}
}
void AnalyticsManager::PUT_analytics_reload(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
/*
* This endpoint must either be overwritten (by adding a custom
* "analyzer/reload" endpoint) or must not be used. A reload requires
* an external io_service object and can therefore not be conducted by the
* AnalyticsManager itself.
*/
res.body() = "Sorry! It seems like this endpoint was not properly implemented.\n";
res.result(http::status::not_implemented);
/*
const std::string plugin = getQuery("plugin", queries);
if (!reload(_io, plugin)) {
res.body() = "Plugin not found or reload failed, please check the config files and MQTT topics!\n";
res.result(http::status::not_found);
} else if (!start(plugin)){
res.body() = "Plugin cannot be restarted!\n";
res.result(http::status::internal_server_error);
} else {
res.body() = "Plugin " + plugin + ": Sensors reloaded";
res.result(http::status::ok);
}
*/
}
void AnalyticsManager::PUT_analytics_compute(endpointArgs) {
if (!managerLoaded(res)) {
return;
}
const std::string plugin = getQuery("plugin", queries);
const std::string analyzer = getQuery("analyzer", queries);
std::string unit = getQuery("unit", queries);
if (plugin == "" || analyzer == "") {
res.body() = "Request malformed: plugin or analyzer query missing\n";
res.result(http::status::bad_request);
return;
}
if (unit == "") {
unit = SensorNavigator::rootKey;
}
res.body() = "Plugin or analyzer not found!\n";
res.result(http::status::not_found);
std::ostringstream data;
bool unitFound=false;
for (const auto &p : _plugins) {
if (p.id == plugin) {
for (const auto &a : p.configurator->getAnalyzers()) {
if( a->getName() == analyzer ) {
std::map<std::string, reading_t> outMap;