Commit 9f7e0524 authored by Micha Mueller's avatar Micha Mueller
Browse files

dcdbpusher: New PluginManager class to bundle all plugin related operations....

dcdbpusher: New PluginManager class to bundle all plugin related operations. Integration outstanding
parent 924570c3
...@@ -27,6 +27,7 @@ OBJS = dcdbpusher.o \ ...@@ -27,6 +27,7 @@ OBJS = dcdbpusher.o \
Configuration.o \ Configuration.o \
MQTTPusher.o \ MQTTPusher.o \
RestAPI.o \ RestAPI.o \
PluginManager.o \
../analytics/AnalyticsManager.o \ ../analytics/AnalyticsManager.o \
../common/src/sensornavigator.o \ ../common/src/sensornavigator.o \
../common/src/globalconfiguration.o \ ../common/src/globalconfiguration.o \
......
/*
* PluginManager.cpp
*
* Created on: 28.05.2019
* Author: Micha Mueller
*/
#include "PluginManager.h"
#include <dlfcn.h>
#include "mqttchecker.h"
using namespace std;
PluginManager::~PluginManager() {
unloadPlugin();
}
bool PluginManager::loadPlugin(const pluginSettings_t& pluginSettings,
const string& name,
const string& pluginPath,
const string& config) {
LOG(info) << "Loading plugin " << name << "...";
string pluginLib; //path to the plugin-lib
string pluginConfig; //path to config file for plugin
// build plugin path
pluginLib = "libdcdbplugin_" + name; //TODO add version information?
#if __APPLE__
pluginLib+= ".dylib";
#else
pluginLib+= ".so";
#endif
if (pluginPath != "") {
if (pluginPath[pluginPath.length()-1] != '/') {
pluginLib = "/" + pluginLib;
}
pluginLib = pluginPath + pluginLib;
}
// build plugin config path
if (config == "") {
pluginConfig = "./" + name + ".conf";
} else {
pluginConfig = config;
}
//open plugin
//dl-code based on http://tldp.org/HOWTO/C++-dlopen/thesolution.html
//TODO switch to C++17 std::filesystem::exists
//check if config file exists
FILE *file = NULL;
if (!(file = fopen(pluginConfig.c_str(), "r"))) {
LOG(warning) << pluginConfig << " not found. Omitting";
return false;
}
fclose(file);
pusherPlugin_t plugin;
plugin.id = name;
plugin.DL = NULL;
plugin.configurator = NULL;
//plugin.conf exists --> open libdcdbplugin_NAME.so and read config
LOG(info) << pluginConfig << " found";
plugin.DL = dlopen(pluginLib.c_str(), RTLD_NOW);
if(!plugin.DL) {
LOG(error) << "Cannot load " << plugin.id << "-library: " << dlerror();
return false;
}
//reset errors
dlerror();
//set dynLib-struct
plugin.create = (create_t*) dlsym(plugin.DL, "create");
const char* dlsym_error = dlerror();
if (dlsym_error) {
LOG(error) << "Cannot load symbol create for " << plugin.id << ": " << dlsym_error;
return false;
}
plugin.destroy = (destroy_t*) dlsym(plugin.DL, "destroy");
dlsym_error = dlerror();
if (dlsym_error) {
LOG(error) << "Cannot load symbol destroy for " << plugin.id << ": " << dlsym_error;
return false;
}
plugin.configurator = plugin.create();
//set prefix to global prefix (may be overwritten)
plugin.configurator->setGlobalSettings(pluginSettings);
//read in config
if (!(plugin.configurator->readConfig(pluginConfig))) {
LOG(error) << "Plugin " << plugin.id << " could not read configuration!";
return false;
}
// returning an empty vector may indicate problems with the config file
if(plugin.configurator->getSensorGroups().size() == 0) {
LOG(warning) << "Plugin " << plugin.id << " created no sensors!";
}
//check if an MQTT-suffix was assigned twice
if (!checkTopics(plugin)) {
LOG(error) << "Problematic MQTT topics or sensor names, please check your config files!";
removeTopics(plugin);
return false;
}
//save dl-struct
_plugins.push_back(plugin);
LOG(info) << "Plugin " << plugin.id << " " << plugin.configurator->getVersion() << " loaded!";
return true;
}
void PluginManager::unloadPlugin(const string& id) {
for (auto it = _plugins.begin(); it != _plugins.end(); ++it) {
if (it->id == id || id == "") {
for (const auto& g : it->configurator->getSensorGroups()) {
g->stop();
}
if (it->configurator) {
it->destroy(it->configurator);
}
if (it->DL) {
dlclose(it->DL);
}
_plugins.erase(it);
}
}
}
bool PluginManager::initPlugin(boost::asio::io_service io,
const string& id) {
bool found = false;
for (const auto& p : _plugins) {
if (p.id == id || id == "") {
for (const auto& g : p.configurator->getSensorGroups()) {
found = true;
g->init(io);
}
}
}
if (!found) {
LOG(warning) << "Could not find plugin " << id << " to initialize!";
} else {
LOG(info) << "Initialized plugin " << id;
}
return found;
}
bool PluginManager::startPlugin(const string& id) {
bool found = false;
for (const auto& p : _plugins) {
if (p.id == id || id == "") {
for (const auto& g : p.configurator->getSensorGroups()) {
found = true;
g->start();
}
}
}
if (!found) {
LOG(warning) << "Could not find plugin " << id << " to start!";
} else {
LOG(info) << "Started plugin " << id;
}
return found;
}
bool PluginManager::stopPlugin(const string& id) {
bool found = false;
for (const auto& p : _plugins) {
if (p.id == id || id == "") {
for (const auto& g : p.configurator->getSensorGroups()) {
found = true;
g->stop();
}
}
}
if (!found) {
LOG(warning) << "Could not find plugin " << id << " to stop!";
} else {
LOG(info) << "Stopped plugin " << id;
}
return found;
}
bool PluginManager::reloadPluginConfig(const string& id) {
for (const auto& p : _plugins) {
if (p.id == id) {
// Removing obsolete MQTT topics
removeTopics(p);
if (p.configurator->reReadConfig()) {
// Perform checks on MQTT topics
if(!checkTopics(p)) {
LOG(warning) << "Plugin " + id + ": problematic MQTT topics or sensor names, please check your config files!";
removeTopics(p);
p.configurator->clearConfig();
return false;
} else {
LOG(info) << "Plugin " + id + ": Configuration reloaded.";
return true;
}
} else {
LOG(warning) << "Plugin " << id << ": Could not reload configuration!";
return false;
}
}
}
return false;
}
bool PluginManager::checkTopics(const pusherPlugin_t& p) {
MQTTChecker& mqttCheck = MQTTChecker::getInstance();
bool validTopics=true;
for(const auto& g : p.configurator->getSensorGroups()) {
if (!mqttCheck.checkGroup(g->getGroupName())) {
validTopics = false;
}
for (const auto& s : g->getSensors()) {
if (!mqttCheck.checkTopic(s->getMqtt()) || !mqttCheck.checkName(s->getName())) {
validTopics = false;
}
}
}
return validTopics;
}
void PluginManager::removeTopics(const pusherPlugin_t& p) {
MQTTChecker& mqttCheck = MQTTChecker::getInstance();
for(const auto& g : p.configurator->getSensorGroups()) {
mqttCheck.removeGroup(g->getGroupName());
for (const auto &s : g->getSensors()) {
mqttCheck.removeTopic(s->getMqtt());
mqttCheck.removeName(s->getName());
}
}
}
/*
* PluginManager.h
*
* Created on: 27.05.2019
* Author: Micha Mueller
*/
#ifndef DCDBPUSHER_PLUGINMANAGER_H_
#define DCDBPUSHER_PLUGINMANAGER_H_
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include "logging.h"
#include "includes/ConfiguratorInterface.h"
/*
* Bundles all attributes to hold a dcdb-pusher plugin aka dynamic library.
*/
typedef struct {
std::string id;
void* DL;
ConfiguratorInterface* configurator;
create_t* create;
destroy_t* destroy;
} pusherPlugin_t;
using pusherPluginStorage_t = std::vector<pusherPlugin_t>;
/**
* @brief This class is responsible of managing plugins for dcdbpusher.
*
*/
class PluginManager {
public:
PluginManager() {};
virtual ~PluginManager();
/**
* @brief Load a new plugin.
*
* @details Searches and loads a shared library file for the plugin as well
* as its corresponding configuration file. The library file name
* is constructed from the plugin name according to
* libdcdbplugin_NAME.so (or .dylib for Apple). After the shared
* library is loaded the plugin reads in its configuration. After
* loading the plugin still has to be initialized (initPlugin())
* before it can be started (startPlugin()).
*
*
* @param pluginSettings Default settings for the plugin.
* @param name Identifying name (ID) of the new plugin.
* @param pluginPath Path where the plugin shared library is located. If
* not specified we will search the default library
* directories (usr/lib and friends).
* @param config Path to plugin configuration file (including config
* file name). If not specified we will search in the
* current directory for "NAME.conf".
*
* @return True on success, false otherwise. In the latter case the plugin
* is not loaded.
*/
bool loadPlugin(const pluginSettings_t& pluginSettings,
const std::string& name,
const std::string& pluginPath = "",
const std::string& config = "");
// Undocumented: if no plugin name is specified all plugins are unloaded.
/**
* @brief Unload a plugin.
*
* @details Stops the specified plugin and removes it completely from the
* manager. To use the plugin again it has to be loaded first.
*
* @param id Identifying name of the plugin.
*/
void unloadPlugin(const std::string& id = "");
// Undocumented: if no plugin name is specified all plugins are initialized.
/**
* @brief Initialize a plugin.
*
* @details Initializes a plugin so it can be started. A plugin has to be
* be initialized only once after it was (re)loaded.
*
* @param io IO service to initialize the plugin's groups with.
* @param id Identifying name of the plugin.
*
* @return True on success, false if the plugin could not be found.
*/
bool initPlugin(boost::asio::io_service io,
const std::string& id = "");
// Undocumented: if no plugin name is specified all plugins are started.
/**
* @brief Start a plugin.
*
* @details Start execution of a previously initialized plugin or resume
* execution of a stopped plugin. An unitialized plugin must not be
* started!
*
* @param id Identifying name of the plugin.
*
* @return True on success, false if the plugin could not be found.
*/
bool startPlugin(const std::string& id = "");
// Undocumented: if no plugin name is specified all plugins are stopped.
/**
* @brief Stop a plugin.
*
* @details Halts execution of the plugin. Can be continued with start().
*
* @param id Identifying name of the plugin.
*
* @return True on success, false if the plugin could not be found.
*/
bool stopPlugin(const std::string& id = "");
/**
* @brief Reload the plugin's configuration.
*
* @details Clears the plugin internal configuration (deleting all of its
* groups and sensors). Reads in the plugin configuration again.
* At the end the plugin is in the same state as after it was
* loaded (i.e. it is not initialized yet). The plugin
* configuration file is expected to be the same (name and
* location) as when the plugin was first loaded.
*
* @param id Identifying name of the plugin.
*
* @return True on success, false otherwise. In the latter case the plugin
* is in an undefined state and should not be started. Failure is
* most likely caused by an invalid new configuration file. Try to
* reload the plugin again with a valid configuration.
*/
bool reloadPluginConfig(const std::string& id);
/**
* @brief Get all currently loaded plugins.
*
* @return Storage of currently loaded plugins.
*/
pusherPluginStorage_t& getPlugins() {
return _plugins;
}
private:
/**
* @brief Utility method to check if the MQTT topics of the given plugin are
* valid.
*
* @param p Plugin.
*
* @return True if all topics are valid, false otherwise.
*/
bool checkTopics(const pusherPlugin_t& p);
/**
* @brief Utility method to remove all MQTT topics associated to a plugin
* from the used set.
*
* @param p Plugin.
*/
void removeTopics(const pusherPlugin_t& p);
pusherPluginStorage_t _plugins; /**< Storage to hold all loaded plugins */
logger_t lg;
};
#endif /* DCDBPUSHER_PLUGINMANAGER_H_ */
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment