// // Created by Netti, Alessio on 10.12.18. // #ifndef PROJECT_ANALYZERCONFIGURATORTEMPLATE_H #define PROJECT_ANALYZERCONFIGURATORTEMPLATE_H #include #include #include #include #include #include #include "AnalyzerTemplate.h" #include "UnitGenerator.h" #include "AnalyzerConfiguratorInterface.h" #include "../../includes/SensorBase.h" #include #include #include #define CFG_VAL boost::property_tree::iptree& /** * Template that implements a standard AnalyzerConfiguratorInterface. * * Users should employ this template whenever possible, and create their own configurators only when strictly * necessary. */ template class AnalyzerConfiguratorTemplate : public AnalyzerConfiguratorInterface { // Verifying the types of input classes static_assert(std::is_base_of::value, "SBase must derive from SensorBase!"); static_assert(std::is_base_of::value, "Analyzer must derive from AnalyzerInterface!"); protected: // For readability using A_Ptr = std::shared_ptr; // Some wildcard characters const char COMMA = ','; const char OPEN_SQBRKET = '['; const char CLOSE_SQBRKET = ']'; const char DASH = '-'; // Keywords used to identify input and output sensor blocks const string INPUT_BLOCK = "input"; const string OUTPUT_BLOCK = "output"; const string ALL_CLAUSE = "all"; const string ALL_REC_CLAUSE = "all-recursive"; public: /** * @brief Class constructor */ AnalyzerConfiguratorTemplate() : _queryEngine(QueryEngine::getInstance()), _analyzerName("INVALID"), _baseName("INVALID"), _cfgPath(""), _mqttPrefix(""), _cacheInterval(900000) {} /** * @brief Copy constructor is not available */ AnalyzerConfiguratorTemplate(const AnalyzerConfiguratorTemplate&) = delete; /** * @brief Assignment operator is not available */ AnalyzerConfiguratorTemplate& operator=(const AnalyzerConfiguratorTemplate&) = delete; /** * @brief Class destructor */ virtual ~AnalyzerConfiguratorTemplate() { for (auto ta : _templateAnalyzers) delete ta.second; _templateAnalyzers.clear(); _analyzerInterfaces.clear(); _analyzers.clear(); } /** * @brief Sets default global settings for analyzers * * This method should be called once after constructing a configurator and before reading * the configuration, so that it has access to the default global settings (which can * be overridden. * * @param pluginSettings struct with global default settings for the plugins. */ virtual void setGlobalSettings(const pluginSettings_t& pluginSettings) final { _mqttPrefix = pluginSettings.mqttPrefix; _cacheInterval = pluginSettings.cacheInterval; } /** * @brief Read a config file and instantiate analyzers accordingly * * This method supplies standard algorithms to instantiate analyzers and parse units from config * files accordingly. It should be overridden only if strictly necessary, which generally should * not happen. * * @param cfgPath Path to the config file * @return True if successful, false otherwise */ bool readConfig(std::string cfgPath) { _cfgPath = cfgPath; _unitGen.setNavigator(_queryEngine.getNavigator()); boost::property_tree::iptree cfg; boost::property_tree::read_info(cfgPath, cfg); // Read global variables (if present overwrite those from global.conf) readGlobal(cfg); // Reading analyzers and template analyzers BOOST_FOREACH(boost::property_tree::iptree::value_type &val, cfg) { // In this block templates are parsed and read if (boost::iequals(val.first, "template_" + _analyzerName)) { LOG(debug) << "Template " << _analyzerName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { Analyzer* an = new Analyzer(val.second.data()); an->setTemplate(true); if (readAnalyzer(*an, val.second)) { auto ret = _templateAnalyzers.insert(std::pair(val.second.data(), an)); if(!ret.second) { LOG(warning) << "Template " << _analyzerName << " " << val.second.data() << " already exists! Omitting..."; delete an; } } else { LOG(warning) << "Template " << _analyzerName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; delete an; } } // Here we read and instantiate analyzers } else if (boost::iequals(val.first, _analyzerName)) { LOG(debug) << _analyzerName << " \"" << val.second.data() << "\""; if (!val.second.empty()) { A_Ptr an = std::make_shared(val.second.data()); if (readAnalyzer(*an, val.second)) { // If the analyzer must be duplicated for each compute unit, we copy-construct identical // instances that have different unit IDs unsigned numUnits = an->getUnits().size(); if(an->getDuplicate() && numUnits>1) { for(unsigned int i=0; i < numUnits; i++) { A_Ptr anCopy = std::make_shared(*an); anCopy->setUnitID(i); storeAnalyzer(anCopy); } } else storeAnalyzer(an); } else { LOG(warning) << _analyzerName << " \"" << val.second.data() << "\" has bad values! Ignoring..."; } } } else if( !boost::iequals(val.first, "global") ) { LOG(error) << "\"" << val.first << "\": unknown construct!"; return false; } } return true; } /** * @brief Clear all instantiated analyzers and read the configuration again * * This will stop any analyzers that have been created, destoy them and finally create new ones * from a new configuration read pass. * * @return True if successful, false otherwise */ bool reReadConfig() final { // Stop all analyzers for(auto a : _analyzers) a->stop(); // Wait for all analyzers to finish for(auto a : _analyzers) a->wait(); // First of all, delete all template analyzers for (auto ta : _templateAnalyzers) delete ta.second; // Clear all analyzers _analyzerInterfaces.clear(); _analyzers.clear(); _templateAnalyzers.clear(); // Reading the configuration once again return readConfig(_cfgPath); } /** * @brief Return all instantiated analyzers * * @return Vector containing pointers to all analyzer interfaces of this plugin */ std::vector& getAnalyzers() final { return _analyzerInterfaces; } protected: /** * @brief Reads any derived analyzer attributes * * Pure virtual interface method, responsible for reading plugin-specific analyzer attributes. * * @param an The analyzer for which derived attributes must be set * @param config A Boost property (sub-)tree containing the config attributes */ virtual void analyzer(Analyzer& an, CFG_VAL config) = 0; /** * @brief Reads any derived sensor attributes * * Pure virtual interface method, responsible for reading plugin-specific sensor attributes. * * @param s The sensor for which derived attributes must be set * @param config A Boost property (sub-)tree containing the config attributes */ virtual void sensorBase(SBase& s, CFG_VAL config) = 0; /** * @brief Performs additional checks on instantiated units * * Pure virtual interface method, responsible for performing user-specified checks on units. * * @param u The unit that has been created * @return True if the unit is valid, False otherwise */ virtual bool unit(UnitTemplate& u) = 0; /** * @brief Reads additional global attributes on top of the default ones * * Virtual interface method, responsible for reading plugin-specific global attributes. * * @param config A Boost property (sub-)tree containing the global values */ virtual void global(CFG_VAL config) {} /** * @brief Store an analyzer in the internal vectors * * @param an Shared pointer to a AnalyzerInterface object */ void storeAnalyzer(A_Ptr an) { _analyzers.push_back(an); _analyzerInterfaces.push_back(an); } /** * @brief Reads a single analyzer configuration block * * Non-virtual interface method for class-internal use only. This will configure an * Analyzer object, and instantiate all units associated to it. All derived attributes * and additional configuration must be performed in the analyzer() virtual method. * * @param an The analyzer that must be configured * @param config A boost property (sub-)tree containing the analyzer values * @return True if successful, false otherwise */ bool readAnalyzer(Analyzer& an, CFG_VAL config) { // Vectors containing "prototype" inputs and outputs to be modified with the actual compute units std::vector protoInputs, protoOutputs; inputMode_t inputMode = SELECTIVE; // Check for the existence of a template definition to initialize the analyzer boost::optional def = config.get_child_optional("default"); if(def) { LOG(debug) << " Using \"" << def.get().data() << "\" as default."; auto it = _templateAnalyzers.find(def.get().data()); if(it != _templateAnalyzers.end()) { an = *(it->second); an.setName(config.data()); an.setTemplate(false); // Analyzers instantiated from templates DO NOT share the same units and sensors. This would lead to // too much naming ambiguity and is generally just not needed an.clearUnits(); } else { LOG(warning) << "Template " << _analyzerName << "\"" << def.get().data() << "\" not found! Using standard values."; } } // Reading attributes associated to AnalyzerInterface BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) { if (boost::iequals(val.first, "interval")) { an.setInterval(stoull(val.second.data())); } else if (boost::iequals(val.first, "minValues")) { an.setMinValues(stoull(val.second.data())); } else if (boost::iequals(val.first, "mqttPart")) { an.setMqttPart(val.second.data()); } else if (boost::iequals(val.first, "sync")) { an.setSync(val.second.data() == "true"); } else if (boost::iequals(val.first, "duplicate")) { an.setDuplicate(val.second.data() == "true"); } else if (boost::iequals(val.first, "streaming")) { an.setStreaming(val.second.data() == "true"); } else if (boost::iequals(val.first, INPUT_BLOCK) || boost::iequals(val.first, OUTPUT_BLOCK)) { // Instantiating all sensors contained within the "input" or "output" block BOOST_FOREACH(boost::property_tree::iptree::value_type &valInner, val.second) { if (boost::iequals(valInner.first, _baseName)) { LOG(debug) << " I/O " << _baseName << " " << valInner.second.data(); SBase sensor = SBase(valInner.second.data()); if (readSensorBase(sensor, valInner.second)) { val.first==INPUT_BLOCK ? protoInputs.push_back(sensor) : protoOutputs.push_back(sensor); } else { LOG(warning) << "I/O " << _baseName << " " << an.getName() << "::" << sensor.getName() << " could not be read! Omitting"; } // An "all" or "all-recursive" statement in the input block causes all sensors related to the specific // unit to be picked } else if (boost::iequals(val.first, INPUT_BLOCK) && (boost::iequals(valInner.first, ALL_CLAUSE) || boost::iequals(valInner.first, ALL_REC_CLAUSE))) { protoInputs.clear(); inputMode = boost::iequals(valInner.first, ALL_CLAUSE) ? ALL : ALL_RECURSIVE; break; } else { LOG(error) << "\"" << valInner.first << "\": unknown I/O construct!"; return false; } } } } // Reading all derived attributes, if any analyzer(an, config); // Instantiating units and returning the result if(!an.getTemplate()) { vector< shared_ptr>> *units=NULL; try { units = _unitGen.generateUnits(protoInputs, protoOutputs, inputMode, _mqttPrefix + an.getMqttPart()); } catch(const std::exception& e) { LOG(error) << _analyzerName << " " << an.getName() << ": Error when creating units: " << e.what(); delete units; return false; } for(auto& u: *units) { LOG(debug) << " Unit \"" << u->getName() << "\""; for(const auto& i : u->getInputs()) LOG(debug) << " Input " << i->getName(); for(const auto& o : u->getOutputs()) LOG(debug) << " Output " << o->getName() << " using MQTT-topic \"" << o->getMqtt() << "\""; if(!unit(*u)) { LOG(error) << " Unit " << u->getName() << " did not pass the final check!"; an.clearUnits(); delete units; return false; } else an.addUnit(u); } delete units; } return true; } /** * @brief Reads a single sensor configuration block * * Non-virtual interface method for class-internal use only. This will configure a * sensor object. All derived attributes and additional configuration must be performed * in the sensorBase() virtual method. * * @param sBase The sensor that must be configured * @param config A boost property (sub-)tree containing the sensor values * @return True if successful, false otherwise */ bool readSensorBase(SBase& sBase, CFG_VAL config) { sBase.setCacheInterval(_cacheInterval); BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) { if (boost::iequals(val.first, "mqttsuffix")) { sBase.setMqtt(val.second.data()); } else if (boost::iequals(val.first, "skipConstVal")) { sBase.setSkipConstVal( val.second.data() == "true" ); } else if (boost::iequals(val.first, "delta")) { sBase.setDelta( val.second.data() == "true" ); } else if (boost::iequals(val.first, "sink")) { sBase.setSinkPath( val.second.data() ); } else if (boost::iequals(val.first, "subSampling")) { sBase.setSubsampling( std::stoul(val.second.data()) ); } } sensorBase(sBase, config); return true; } /** * @brief Reads the global configuration block * * Non-virtual interface method for class-internal use only. This will read the "global" * configuration block in a file, overwriting any default settings on a per-plugin base. * Any derived or additional attributes must be added through the global() virtual method. * * @param config A Boost property (sub-)tree containing the global block * @return True if successful, false otherwise */ bool readGlobal(CFG_VAL config) { boost::optional globalVals = config.get_child_optional("global"); if (globalVals) { BOOST_FOREACH(boost::property_tree::iptree::value_type &global, config.get_child("global")) { if (boost::iequals(global.first, "mqttprefix")) { _mqttPrefix = global.second.data(); if (_mqttPrefix[_mqttPrefix.length()-1] != '/') { _mqttPrefix.append("/"); } LOG(debug) << " Using own MQTT-Prefix " << _mqttPrefix; } else if (boost::iequals(global.first, "cacheInterval")) { _cacheInterval = stoul(global.second.data()); LOG(debug) << " Using own caching interval " << _cacheInterval << " [s]"; _cacheInterval *= 1000; } } global(config.get_child("global")); } return true; } // Instance of a QueryEngine object QueryEngine& _queryEngine; // UnitGenerator object used to create units UnitGenerator _unitGen; // Keyword used to identify analyzer blocks in config files std::string _analyzerName; // Keyword used to identify sensors in config files std::string _baseName; // Path of the configuration file that must be used std::string _cfgPath; // Default MQTT prefix to be used when creating output sensors std::string _mqttPrefix; // Interval in seconds for the cache of each sensor unsigned int _cacheInterval; // The vector of analyzers, in the form of pointers to AnalyzerInterface objects std::vector _analyzerInterfaces; // Like the above, but containing the analyzers in their actual types std::vector _analyzers; // Map of the template analyzers that were defined in the config file - used for easy retrieval and instantiation std::map _templateAnalyzers; }; #endif //PROJECT_ANALYZERCONFIGURATORTEMPLATE_H