ConfiguratorTemplate.h 32.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//================================================================================
// Name        : ConfiguratorTemplate.h
// Author      : Micha Mueller, Caral Guillen
// Copyright   : Leibniz Supercomputing Centre
// Description : Interface template for plugin configurators.
//================================================================================

//================================================================================
// This file is part of DCDB (DataCenter DataBase)
// Copyright (C) 2018-2019 Leibniz Supercomputing Centre
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//================================================================================
26

27
28
#ifndef SRC_CONFIGURATORTEMPLATE_H_
#define SRC_CONFIGURATORTEMPLATE_H_
29

30
#include <map>
Alessio Netti's avatar
Alessio Netti committed
31
#include <set>
32
33
34
35
36

#include <boost/foreach.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/info_parser.hpp>
Alessio Netti's avatar
Alessio Netti committed
37
#include <boost/regex.hpp>
38
#include "ConfiguratorInterface.h"
39
#include "sensorbase.h"
40
#include "SensorGroupTemplate.h"
41
#include "version.h"
42

43
#include <algorithm>
Alessio Netti's avatar
Alessio Netti committed
44
45
46
47
#include <iostream>
#include <sstream>
#include <iomanip>

48
//#define STRCMP(node,str) boost::iequals(node.first,str) //DEPRECATED
Micha Mueller's avatar
Micha Mueller committed
49
#define CFG_VAL	boost::property_tree::iptree&
50

51
#define ADD						BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config)
52
#define ATTRIBUTE(name,setter)		do { if (boost::iequals(val.first, name)) { s.setter(val.second.data()); } } while(0)
Micha Mueller's avatar
Micha Mueller committed
53
#define SETTING(name)				if (boost::iequals(val.first, name))
54

55
56
#define DEFAULT_CACHE_INTERVAL 900000

57
58
59
60
61
/**
 * @brief Interface template for plugin configurator implementations.
 *
 * @ingroup pusherplugins
 */
62
template <class SBase, class SGroup, class SEntity = nullptr_t>
63
class ConfiguratorTemplate : public ConfiguratorInterface {
64
	//the template shall only be instantiated for classes which derive from SensorBase/SensorGroup
65
	static_assert(std::is_base_of<SensorBase, SBase>::value, "SBase must derive from SensorBase!");
66
	static_assert(std::is_base_of<SensorGroupInterface, SGroup>::value, "SGroup must derive from SensorGroupInterface!");
67

68
protected:
Michael Ott's avatar
Michael Ott committed
69
	typedef std::map<std::string, SBase*> sBaseMap_t;
70
71
	typedef std::map<std::string, SGroup*> sGroupMap_t;
	typedef std::map<std::string, SEntity*> sEntityMap_t;
72

73
74
75
	using SB_Ptr = std::shared_ptr<SBase>;
	using SG_Ptr = std::shared_ptr<SGroup>;

76
77
78
79
80
	const char COMMA = ',';
	const char OPEN_SQBRKET = '[';
	const char CLOSE_SQBRKET = ']';
	const char DASH = '-';

Alessio Netti's avatar
Alessio Netti committed
81
82
	const std::string SENSOR_PATTERN = "(?i)<sensor>";
	const std::string GROUP_PATTERN = "(?i)<group>";
Alessio Netti's avatar
Alessio Netti committed
83

84
public:
85
	ConfiguratorTemplate() :
86
87
88
		_entityName("INVALID"),
		_groupName("INVALID"),
		_baseName("INVALID"),
89
90
		_cfgPath(""),
		_mqttPrefix(""),
Alessio Netti's avatar
Alessio Netti committed
91
		_sensorPattern(""),
92
		_cacheInterval(DEFAULT_CACHE_INTERVAL) {}
93

94
95
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

96
	virtual ~ConfiguratorTemplate() {
97
98
99
		for (auto e : _sensorEntitys) {
			delete e;
		}
Michael Ott's avatar
Michael Ott committed
100
101
102
		for (auto tb : _templateSensorBases) {
			delete tb.second;
		}
103
		for (auto tg : _templateSensorGroups) {
104
			delete tg.second;
105
106
		}
		for (auto te : _templateSensorEntitys) {
107
			delete te.second;
108
		}
109
		_sensorGroupInterfaces.clear();
110
111
		_sensorGroups.clear();
		_sensorEntitys.clear();
Michael Ott's avatar
Michael Ott committed
112
		_templateSensorBases.clear();
113
		_templateSensorGroups.clear();
114
		_templateSensorEntitys.clear();
115
	}
116

117
118
	ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete;

119
120
121
	std::string getVersion() {
		return std::string(VERSION);
	}
Micha Mueller's avatar
Micha Mueller committed
122
123
	/**
	 * Read in the given configuration
124
	 *
125
126
	 * Overwriting this method is only required if a custom logic is really necessary!
	 *
Micha Mueller's avatar
Micha Mueller committed
127
	 * @param	cfgPath Path to the config-file
128
129
	 *
	 * @return	True on success, false otherwise
Micha Mueller's avatar
Micha Mueller committed
130
	 */
131
	bool readConfig(std::string cfgPath) {
132
		_cfgPath = cfgPath;
133
134
135
136
137

		boost::property_tree::iptree cfg;
		boost::property_tree::read_info(cfgPath, cfg);

		//read global variables (if present overwrite those from global.conf)
138
139
		readGlobal(cfg);

140
141
142
143
144
145
		//read groups and templates for groups. If present also entity/-template stuff
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, cfg) {
			//template entity
			if (boost::iequals(val.first, "template_" + _entityName)) {
				LOG(debug) << "Template " << _entityName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
Micha Mueller's avatar
Micha Mueller committed
146
147
148
					//name of an entity is only used to identify templates and is not stored otherwise
					SEntity* entity = new SEntity();
					if (readSensorEntity(*entity, val.second, true)) {
149
						auto ret = _templateSensorEntitys.insert(std::pair<std::string, SEntity*>(val.second.data(), entity));
150
						if(!ret.second) {
151
							LOG(warning) << "Template " << _entityName << " " << val.second.data() << " already exists! Omitting...";
Micha Mueller's avatar
Micha Mueller committed
152
							delete entity;
153
154
155
						}
					} else {
						LOG(warning) << "Template " << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
Micha Mueller's avatar
Micha Mueller committed
156
						delete entity;
157
158
159
160
161
162
163
					}
				}
			//template group
			} else if (boost::iequals(val.first, "template_" + _groupName)) {
				LOG(debug) << "Template " << _groupName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
					SGroup* group = new SGroup(val.second.data());
164
					if (readSensorGroup(*group, val.second, true)) {
165
						auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
166
						if(!ret.second) {
167
							LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
Micha Mueller's avatar
Micha Mueller committed
168
							delete group;
169
						}
170
171
					} else {
						LOG(warning) << "Template " << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
Micha Mueller's avatar
Micha Mueller committed
172
						delete group;
173
174
					}
				}
Michael Ott's avatar
Michael Ott committed
175
176
177
178
179
			//template base
			} else if (boost::iequals(val.first, "template_" + _baseName)) {
				LOG(debug) << "Template " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
					SBase* base = new SBase(val.second.data());
180
					if (readSensorBase(*base, val.second, true)) {
Michael Ott's avatar
Michael Ott committed
181
182
183
184
185
186
187
188
189
190
						auto ret = _templateSensorBases.insert(std::pair<std::string, SBase*>(val.second.data(), base));
						if(!ret.second) {
							LOG(warning) << "Template " << _baseName << " " << val.second.data() << " already exists! Omitting...";
							delete base;
						}
					} else {
						LOG(warning) << "Template " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
						delete base;
					}
				}
191
192
193
194
195
			//template single sensor
			} else if (boost::iequals(val.first, "template_single_" + _baseName)) {
				LOG(debug) << "Template single " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
					SGroup* group = new SGroup(val.second.data());
196
					if (readSensorGroup(*group, val.second, true)) {
197
						//group which consists of only one sensor
198
						SB_Ptr sensor = std::make_shared<SBase>(val.second.data());
199
						if (readSensorBase(*sensor, val.second, true)) {
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
							group->pushBackSensor(sensor);
							auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
							if(!ret.second) {
								LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " already exists! Omitting...";
								delete group;
							}
						} else {
							LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
							delete group;
						}
					} else {
						LOG(warning) << "Template single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
						delete group;
					}
				}
215
216
217
218
			//entity
			} else if (boost::iequals(val.first, _entityName)) {
				LOG(debug) << _entityName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
Micha Mueller's avatar
Micha Mueller committed
219
220
					SEntity* entity = new SEntity();
					if (readSensorEntity(*entity, val.second, false)) {
221
222
223
						_sensorEntitys.push_back(entity);
					} else {
						LOG(warning) << _entityName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
Micha Mueller's avatar
Micha Mueller committed
224
						delete entity;
225
					}
226
227
228
229
230
				}
			//group
			} else if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << _groupName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
231
					SG_Ptr group = std::make_shared<SGroup>(val.second.data());
232
					if (readSensorGroup(*group, val.second)) {
233
						storeSensorGroup(group);
234
235
236
237
					} else {
						LOG(warning) << _groupName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
238
239
240
241
			//single sensor
			} else if (boost::iequals(val.first, "single_" + _baseName)) {
				LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
242
					SG_Ptr group = std::make_shared<SGroup>(val.second.data());
243
244
					if (readSensorGroup(*group, val.second)) {
						//group which consists of only one sensor
245
						SB_Ptr sensor;
246
247
						//perhaps one sensor is already present because it was copied from the template group
						if (group->getSensors().size() != 0) {
248
249
250
251
252
253
254
255
256
							sensor = std::dynamic_pointer_cast<SBase>(group->getSensors()[0]);
							//check if cast was successful (sensor != nullptr)
							if (sensor) {
								sensor->setName(val.second.data());
								if (readSensorBase(*sensor, val.second)) {
									storeSensorGroup(group);
								} else {
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
								}
257
							} else {
258
								LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting";
259
260
							}
						} else {
261
							sensor = std::make_shared<SBase>(val.second.data());
262
263
264
265
266
267
268
269
270
271
272
							if (readSensorBase(*sensor, val.second)) {
								group->pushBackSensor(sensor);
								storeSensorGroup(group);
							} else {
								LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
							}
						}
					} else {
						LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
					}
				}
Alessio Netti's avatar
Alessio Netti committed
273
			} else if( !boost::iequals(val.first, "global") ) {
274
275
			    LOG(error) << "\"" << val.first << "\": unknown construct!";
			    return false;
276
277
			}
		}
278
		//read of config finished. Now we build the mqtt-topic for every sensor
Alessio Netti's avatar
Alessio Netti committed
279
280
		if(!constructSensorNames())
			return false;
281
282
		for(const auto& g : _sensorGroups) {
			for(const auto& s : g->getSensors()) {
283
284
285
				s->setMqtt(_mqttPrefix + g->getMqttPart() + s->getMqtt());
			}
		}
286
		return true;
287
288
289
	}

	/**
Alessio Netti's avatar
Alessio Netti committed
290
	 * Clear internal storage and returns the plugin to the uninitialized state.
291
	 */
Alessio Netti's avatar
Alessio Netti committed
292
	void clearConfig() final {
Micha Mueller's avatar
Micha Mueller committed
293
294
		//bring everything to a halt
		for(auto g : _sensorGroups) {
295
			g->stop();
Micha Mueller's avatar
Micha Mueller committed
296
297
298
299
300
301
		}

		//wait until everything is halted
		for(auto g : _sensorGroups) {
			g->wait();
		}
302

303
304
305
306
		//clean up sensors/groups/entitys and templates
		for(auto e : _sensorEntitys) {
			delete e;
		}
Michael Ott's avatar
Michael Ott committed
307
308
309
		for (auto tb : _templateSensorBases) {
			delete tb.second;
		}
310
		for (auto tg : _templateSensorGroups) {
311
			delete tg.second;
312
313
		}
		for (auto te : _templateSensorEntitys) {
314
			delete te.second;
315
		}
316
		_sensorGroupInterfaces.clear();
Micha Mueller's avatar
Micha Mueller committed
317
		_sensorGroups.clear();
318
		_sensorEntitys.clear();
Michael Ott's avatar
Michael Ott committed
319
		_templateSensorBases.clear();
320
		_templateSensorGroups.clear();
321
		_templateSensorEntitys.clear();
Alessio Netti's avatar
Alessio Netti committed
322
323
324
325
326
327
328
329
330
	}

	/**
	 * Clear internal storage and read in the configuration again.
	 *
	 * @return	True on success, false otherwise
	 */
	bool reReadConfig() final {
		clearConfig();
331

Micha Mueller's avatar
Micha Mueller committed
332
		//back to the very beginning
333
		return readConfig(_cfgPath);
334
	}
335

336
337
338
339
340
	/**
	 * Print configuration as read in.
	 *
	 * @param ll    Logging level to log with
	 */
341
	void printConfig(LOG_LEVEL ll) final {
342
	  LOG_VAR(ll) << "    General: ";
343
	  if (_mqttPrefix != "") {
344
	    LOG_VAR(ll) << "        MQTT-Prefix: " << _mqttPrefix;
345
	  } else {
346
	    LOG_VAR(ll) << "        MQTT-Prefix: DEFAULT";
347
348
	  }
	  if (_sensorPattern != "") {
349
	    LOG_VAR(ll) << "        Sensor Pattern: " << _sensorPattern;
350
351
	  }
	  if (_cacheInterval != DEFAULT_CACHE_INTERVAL) {
352
	    LOG_VAR(ll) << "        Cache interval: " << _cacheInterval << " ms";
353
	  } else {
354
	    LOG_VAR(ll) << "        Cache interval: DEFAULT";
355
356
	  }

357
	  //prints plugin specific configurator attributes and entities if present
358
359
	  printConfiguratorConfig(ll);

360
	  LOG_VAR(ll) << "    Groups:";
361
	  for(auto g : _sensorGroups) {
362
363
364
	    g->SensorGroupInterface::printConfig(ll);
	    g->printConfig(ll);
	    g->SensorGroupTemplate<SBase>::printConfig(ll);
365
366
367
	  }
	}

368
369
370
371
372
373
374
	/**
	 * Sets internal variables with the ones provided by pluginSettings.
	 * This method should be called once after constructing a configurator
	 * to provide him with the global default values.
	 *
	 * @param pluginSettings	Struct with global default settings for the plugins.
	 */
375
	void setGlobalSettings(const pluginSettings_t& pluginSettings) final {
376
		_mqttPrefix = pluginSettings.mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
377
		_sensorPattern = pluginSettings.sensorPattern;
378
		_cacheInterval = pluginSettings.cacheInterval;
379
380

		derivedSetGlobalSettings(pluginSettings);
381
382
	}

Micha Mueller's avatar
Micha Mueller committed
383
384
385
386
387
	/**
	 * Get all sensor groups
	 *
	 * @return	Vector containing pointers to all sensor groups of this plugin
	 */
388
	std::vector<SGroupPtr>& getSensorGroups() final {
389
390
391
		return _sensorGroupInterfaces;
	}

392
protected:
393
	void storeSensorGroup(SG_Ptr sGroup) {
394
395
		_sensorGroups.push_back(sGroup);
		_sensorGroupInterfaces.push_back(sGroup);
Micha Mueller's avatar
Micha Mueller committed
396
397
	}

398
399
	/**
	 * Non-virtual interface method for class-internal use only.
400
	 * Reads and sets the common base values of a sensor base (currently none),
401
402
	 * then calls the corresponding derived function to read plugin specific
	 * values.
403
	 *
404
	 * @param sBase		The sensor base for which to set the values
405
	 * @param config	A boost property (sub-)tree containing the sensor values
406
	 * @param isTemplate	Are we parsing a template or a regular sensor?
407
408
409
	 *
	 * @return	True on success, false otherwise
	 */
410
	bool readSensorBase(SBase& sBase, CFG_VAL config, bool isTemplate=false) {
411
		sBase.setCacheInterval(_cacheInterval);
412
413
414
		if (!isTemplate) {
			boost::optional<boost::property_tree::iptree&> def = config.get_child_optional("default");
			if(def) {
415
				//we copy all values from default
416
417
418
419
420
421
422
423
				LOG(debug) << "  Using \"" << def.get().data() << "\" as default.";
				auto it = _templateSensorBases.find(def.get().data());
				if(it != _templateSensorBases.end()) {
					sBase = *(it->second);
					sBase.setName(config.data());
				} else {
					LOG(warning) << "Template " << _baseName << "\" " << def.get().data() << "\" not found! Using standard values.";
				}
Michael Ott's avatar
Michael Ott committed
424
425
426
			}
		}
		
427
428
429
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, "mqttsuffix")) {
				sBase.setMqtt(val.second.data());
430
			} else if (boost::iequals(val.first, "skipConstVal")) {
Alessio Netti's avatar
Alessio Netti committed
431
				sBase.setSkipConstVal(to_bool(val.second.data()));
432
			} else if (boost::iequals(val.first, "delta")) {
Alessio Netti's avatar
Alessio Netti committed
433
				sBase.setDelta(to_bool(val.second.data()));
434
435
436
			} else if (boost::iequals(val.first, "sink")) {
				sBase.setSinkPath( val.second.data() );
			} else if (boost::iequals(val.first, "subSampling")) {
Alessio Netti's avatar
Alessio Netti committed
437
				sBase.setSubsampling(std::stoul(val.second.data()));
438
			}
439
440
441
		}
		sensorBase(sBase, config);
		return true;
442
443
444
445
	}

	/**
	 * Non-virtual interface method for class-internal use only.
446
447
	 * Reads and sets the common base values of a sensor group, then calls
	 * the corresponding derived function to read in plugin specific values.
448
	 *
449
	 * @param sGroup	The sensor group for which to set the values
450
	 * @param config	A boost property (sub-)tree containing the sensor values
451
	 * @param isTemplate	Is this a template or a regular group?
452
453
454
	 *
	 * @return	True on success, false otherwise
	 */
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
	bool readSensorGroup(SGroup& sGroup, CFG_VAL config, bool isTemplate=false) {
		//first check if default group is given, unless we are reading a template already
		if (!isTemplate) {
			boost::optional<boost::property_tree::iptree&> def = config.get_child_optional("default");
			if(def) {
				//we copy all values from default (including copy constructing its sensors)
				//if own sensors are specified they are appended
				LOG(debug) << "  Using \"" << def.get().data() << "\" as default.";
				auto it = _templateSensorGroups.find(def.get().data());
				if(it != _templateSensorGroups.end()) {
					sGroup = *(it->second);
					sGroup.setGroupName(config.data());
				} else {
					LOG(warning) << "Template " << _groupName << "\" " << def.get().data() << "\" not found! Using standard values.";
				}
470
471
472
			}
		}

473
		//read in values inherited from SensorGroupInterface
474
		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
475
			if (boost::iequals(val.first, "interval")) {
476
				sGroup.setInterval(stoull(val.second.data()));
477
			} else if (boost::iequals(val.first, "minValues")) {
478
				sGroup.setMinValues(stoull(val.second.data()));
479
480
			} else if (boost::iequals(val.first, "mqttPart")) {
				sGroup.setMqttPart(val.second.data());
481
482
483
484
485
486
			} else if (boost::iequals(val.first, "sync")) {
				if (val.second.data() == "off") {
					sGroup.setSync(false);
				} else {
					sGroup.setSync(true);
				}
487
			} else if (boost::iequals(val.first, _baseName)) {
488
489
490
				if (!isTemplate) {
					LOG(debug) << "  " << _baseName << " " << val.second.data();
				}
491
				SB_Ptr sensor = std::make_shared<SBase>(val.second.data());
492
				if (readSensorBase(*sensor, val.second, isTemplate)) {
493
					sGroup.pushBackSensor(sensor);
494
				} else if (!isTemplate) {
495
					LOG(warning) << _baseName << " " << sGroup.getGroupName() << "::" << sensor->getName() << " could not be read! Omitting";
496
				}
497
498
499
			}
		}

500
501
502
		//TODO keep debug logging for config?
//		LOG(debug) << "  Interval : " << sGroup.getInterval();
//		LOG(debug) << "  minValues: " << sGroup.getMinValues();
503

504
505
		sensorGroup(sGroup, config);
		return true;
506
507
	}

508
509
510
511
512
513
514
	/**
	 * Non-virtual interface method for class-internal use only.
	 * Reads and sets the common base values of a sensor entity, then calls
	 * the corresponding derived function to read in plugin specific values.
	 *
	 * @param sEntity	The aggregating entity for which to set the values
	 * @param config	A boost property (sub-)tree containing the sensor values
Micha Mueller's avatar
Micha Mueller committed
515
516
	 * @param isTemplate	Indicate if sEntity is a template. If so, also store
	 * 						the corresponding sGroups in the template map
517
518
519
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
520
	bool readSensorEntity(SEntity& sEntity, CFG_VAL config, bool isTemplate) {
521
522
523
524
525
526
527
		//first check if default entity is given
		boost::optional<boost::property_tree::iptree&> def = config.get_child_optional("default");
		if(def) {
			//we copy all values from default
			LOG(debug) << "  Using \"" << def.get().data() << "\" as default.";
			auto it = _templateSensorEntitys.find(def.get().data());
			if(it != _templateSensorEntitys.end()) {
528
				sEntity = *(it->second);
Micha Mueller's avatar
Micha Mueller committed
529
530
				for(auto g : _templateSensorGroups) {
					if (isEntityOfGroup(*(it->second), *(g.second))) {
531
						SG_Ptr group = std::make_shared<SGroup>(*(g.second));
Micha Mueller's avatar
Micha Mueller committed
532
						setEntityForGroup(sEntity, *group);
533
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
534
535
					}
				}
536
537
538
539
			} else {
				LOG(warning) << "Template " << _entityName << "\"" << def.get().data() << "\" not found! Using standard values.";
			}
		}
Micha Mueller's avatar
Micha Mueller committed
540

541
		sensorEntity(sEntity, config);
Micha Mueller's avatar
Micha Mueller committed
542
543
544
545

		BOOST_FOREACH(boost::property_tree::iptree::value_type &val, config) {
			if (boost::iequals(val.first, _groupName)) {
				LOG(debug) << "  " << _groupName << " " << val.second.data();
546
				if (!val.second.empty()) {
547
548
549
550
					if (isTemplate) {
						SGroup* group = new SGroup(val.second.data());
						if(readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
551
552
553
554
555
556
							auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
							if(!ret.second) {
								LOG(warning) << "Template " << _groupName << " " << val.second.data() << " already exists! Omitting...";
								delete group;
							}
						} else {
557
558
							LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
							delete group;
Micha Mueller's avatar
Micha Mueller committed
559
560
						}
					} else {
561
562
563
564
565
566
567
						SG_Ptr group = std::make_shared<SGroup>(val.second.data());
						if(readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
							storeSensorGroup(group);
						} else {
							LOG(warning) << _groupName << " " << group->getGroupName() << " could not be read! Omitting";
						}
568
569
570
571
572
					}
				}
			} else if (boost::iequals(val.first, "single_" + _baseName)) {
				LOG(debug) << "Single " << _baseName << " \"" << val.second.data() << "\"";
				if (!val.second.empty()) {
573
574
575
576
577
578
					if (isTemplate) {
						SGroup* group = new SGroup(val.second.data());
						//group which consists of only one sensor
						if (readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
							SB_Ptr sensor = std::make_shared<SBase>(val.second.data());
579
580
581
582
583
584
585
586
587
588
589
590
							if (readSensorBase(*sensor, val.second)) {
								group->pushBackSensor(sensor);
								auto ret = _templateSensorGroups.insert(std::pair<std::string, SGroup*>(val.second.data(), group));
								if(!ret.second) {
									LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " already exists! Omitting...";
									delete group;
								}
							} else {
								LOG(warning) << "Template single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
								delete group;
							}
						} else {
591
592
593
594
595
596
597
598
599
							LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
							delete group;
						}
					} else {
						SG_Ptr group = std::make_shared<SGroup>(val.second.data());
						//group which consists of only one sensor
						if (readSensorGroup(*group, val.second)) {
							setEntityForGroup(sEntity, *group);
							SB_Ptr sensor;
600
601
							//perhaps one sensor is already present because it was copied from the template group
							if (group->getSensors().size() != 0) {
602
603
604
605
606
607
608
609
610
								sensor = std::dynamic_pointer_cast<SBase>(group->getSensors()[0]);
								//check if cast was successful (sensor != nullptr)
								if (sensor) {
									sensor->setName(val.second.data());
									if (readSensorBase(*sensor, val.second)) {
										storeSensorGroup(group);
									} else {
										LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
									}
611
								} else {
612
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " had a type mismatch when casting! Omitting";
613
614
								}
							} else {
615
								sensor = std::make_shared<SBase>(val.second.data());
616
617
618
619
620
621
622
								if (readSensorBase(*sensor, val.second)) {
									group->pushBackSensor(sensor);
									storeSensorGroup(group);
								} else {
									LOG(warning) << "Single " << _baseName << " " << val.second.data() << " could not be read! Omitting";
								}
							}
623
624
						} else {
							LOG(warning) << "Single " << _baseName << " \"" << val.second.data() << "\" has bad values! Ignoring...";
625
						}
Micha Mueller's avatar
Micha Mueller committed
626
627
628
629
630
					}
				}
			}
		}

631
		if(!isTemplate) {
632
			for(const auto& g : _sensorGroups) {
633
634
635
				if(isEntityOfGroup(sEntity, *g)) {
					finalizeGroup(*g);
				}
Micha Mueller's avatar
Micha Mueller committed
636
637
			}
		}
638
		return true;
639
640
	}

Micha Mueller's avatar
Micha Mueller committed
641
	bool readGlobal(CFG_VAL config) {
642
643
644
645
646
647
648
649
650
651
652
		boost::optional<boost::property_tree::iptree&> 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("/");
					}
				} else if (boost::iequals(global.first, "cacheInterval")) {
					_cacheInterval = stoul(global.second.data());
					_cacheInterval *= 1000;
Alessio Netti's avatar
Alessio Netti committed
653
654
				} else if (boost::iequals(global.first, "sensorpattern")) {
					_sensorPattern = global.second.data();
655
656
				}
			}
657
			global(config.get_child("global"));
658
		}
659
		return true;
660
661
	}

662
	/**
663
	 * Virtual interface method, responsible for setting global values specifically
664
665
666
667
	 * for its plugin.
	 *
	 * @param pluginSettings	The struct with global default plugin settings
	 */
668
669
670
	virtual void derivedSetGlobalSettings(const pluginSettings_t& pluginSettings) {
		//Overwrite if necessary
	}
671
672
673

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
674
	 * base values.
675
	 *
Micha Mueller's avatar
Micha Mueller committed
676
	 * @param s			The sensor base for which to set the values
677
678
	 * @param config	A boost property (sub-)tree containing the sensor values
	 */
Micha Mueller's avatar
Micha Mueller committed
679
	virtual void sensorBase(SBase& s, CFG_VAL config) = 0;
680
681
682
683
684

	/**
	 * Pure virtual interface method, responsible for reading plugin-specific sensor
	 * group values.
	 *
Micha Mueller's avatar
Micha Mueller committed
685
	 * @param s			The sensor group for which to set the values
686
	 * @param config	A boost property (sub-)tree containing the group values
687
	 */
Micha Mueller's avatar
Micha Mueller committed
688
	virtual void sensorGroup(SGroup& s, CFG_VAL config) = 0;
689

690
691
692
693
	/**
	 * Virtual interface method, responsible for reading plugin-specific sensor
	 * entity values.
	 *
Micha Mueller's avatar
Micha Mueller committed
694
	 * @param s			The sensor entity for which to set the values
695
696
	 * @param config	A boost property (sub-)tree containing the entity values
	 */
Micha Mueller's avatar
Micha Mueller committed
697
	virtual void sensorEntity(SEntity& s, CFG_VAL config) {
698
699
700
		//Overwrite if necessary
		LOG(warning) << "Method sensorEntity called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method";
	}
701

Micha Mueller's avatar
Micha Mueller committed
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
	/**
	 * Check if e is the corresponding entity of g
	 *
	 * @param e
	 * @param g
	 *
	 * @return	True if (g.entity == &e)
	 */
	//TODO not very convenient for writing plugins. Are there better solutions?
	virtual bool isEntityOfGroup(SEntity& e, SGroup& g) {
		//Overwrite if necessary
		LOG(warning) << "Method isEntityOfGroup called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method";
		return false;
	}

	/**
	 * Sets e as entity for group g
	 *
	 * @param e
	 * @param g
	 */
	//TODO not very convenient for writing plugins. Are there better solutions?
	virtual void setEntityForGroup(SEntity& e, SGroup& g) {
		//Overwrite if necessary
		LOG(warning) << "Method setEntityForGroup called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method";
	}

	/**
	 * Finalize the group g with everything it needs from its entity (set e.g. the mqttPart for entity)
	 *
	 * @param g
	 */
	//TODO not very convenient for writing plugins. Are there better solutions?
	virtual void finalizeGroup(SGroup& g) {
		//Overwrite if necessary
		LOG(warning) << "Method finalizeEntityGroup called, but was not overwritten! Either you have unwanted entitys in your config file or forgot to overwrite this method";
	}

740
741
	/**
	 * Print information about configurable configurator attributes (or nothing if no such attributes are required)
742
	 * and associated entities.
743
744
745
746
747
	 *
	 * @param ll    Severity level to log with
	 */
	virtual void printConfiguratorConfig(LOG_LEVEL ll) {
	  //Overwrite if necessary
748
	  LOG_VAR(ll) << "        No other plugin-specific general parameters or entities defined";
749
750
	}

751
752
753
754
755
	/**
	 * Virtual interface method, responsible for reading plugin-specific global values.
	 *
	 * @param config	A boost property (sub-)tree containing the global values
	 */
Micha Mueller's avatar
Micha Mueller committed
756
	virtual void global(CFG_VAL config) {}
757

Alessio Netti's avatar
Alessio Netti committed
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
	/**
	 * Increases by a certain value the input MQTT hex topic.
	 *
	 * Example: a mqtt="AAB7" and val=5 produce "AAC2" as output.
	 *
	 * @param mqtt: the MQTT hex string whose value has to be increased
	 * @param val: the value by which mqtt has to be increased
	 *
	 * @return the increased MQTT string
	 *
	 */
	const std::string increaseMqtt(const std::string& mqtt, int val) {
		unsigned long mqttDigits = stoul(mqtt, 0, 16);
		mqttDigits += val;
		std::stringstream stream;
		stream << std::setfill ('0') << std::setw(mqtt.length()) << std::uppercase << std::hex << mqttDigits;
		return stream.str();
	}

	/**
778
779
780
	 * Replaces occurences of 'x' characters by a hex representation of a
	 * numerical CPU core ID. If no 'x' characters are found only the CPU hex
	 * string with specified width is returned.
Alessio Netti's avatar
Alessio Netti committed
781
	 *
782
783
784
	 * Examples: mqttPart=  "xx", val=11 --> return   "0B"
	 *           mqttPart="A3xx", val=11 --> return "A30B"
	 *           mqttPart="A3YY", val=11 --> return "000B"
Alessio Netti's avatar
Alessio Netti committed
785
786
787
788
789
790
791
792
793
	 *
	 * @param mqttPart: a template MQTT string, defines the length of the final string
	 * @param val: the value of the CPU core ID
	 *
	 * @return the hex string representation of the input CPU core ID
	 *
	 */
	const std::string formatMqttCPU(const std::string& mqttPart, unsigned int val) {
		std::stringstream stream;
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810

		size_t n = std::count(mqttPart.begin(), mqttPart.end(), 'x');

		if (n==0) {
            stream << std::setfill ('0') << std::setw(mqttPart.length()) << std::uppercase << std::hex << val;
            return stream.str();
		} else {
		    std::string result(mqttPart);

		    stream << std::setfill ('0') << std::setw(n) << std::uppercase << std::hex << val;
		    std::string replacement = stream.str();
		    std::string pattern(n, 'x');

		    size_t index = result.find(pattern, index);
            result.replace(index, n, replacement);
            return result;
		}
Alessio Netti's avatar
Alessio Netti committed
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
	}

	/**
	 * Tries to parse the given cpuString as integer numbers. On success, the specified numbers will be inserted
	 * into a set, which will be returned. On failure, an empty set is returned. A set is used to maintain uniqueness
	 * and an ascending order among the numbers although this is not strictly required.
	 *
	 * @param cpuString	String which specifies a range and/or set of numbers (e.g. "1,2,3-5,7-9,10")
	 * @return	A set of integers as specified in the cpuString. If the string could not be parsed the set will be empty.
	 */
	std::set<int> parseCpuString(const std::string& cpuString) {
		std::set<int> cpus;
		int maxCpu = 512;

		std::vector<std::string> subStrings;

		std::stringstream ssComma(cpuString);
		std::string item;
		while (std::getline(ssComma, item, ',')) {
			subStrings.push_back(item);
		}

		for (auto s : subStrings) {
			if (s.find('-') != std::string::npos) { //range of values (e.g. 1-5) specified
				std::stringstream ssHyphen(s);
				std::string min, max;
				std::getline(ssHyphen, min, '-');
				std::getline(ssHyphen, max);

				try {
					int minVal = stoi(min);
					int maxVal = stoi(max);

					for (int i = minVal; i <= maxVal; i++) {
						if (i >= 0 && i < maxCpu) {
							cpus.insert((int)i);
						}
					}
				} catch (const std::exception& e) {
					LOG(debug) << "Could not parse values \"" << min << "-" << max << "\"";
				}
			} else { //single value
				try {
					int val = stoi(s);
					if (val >= 0 && val < maxCpu) {
						cpus.insert((int)val);
					}
				} catch (const std::exception& e) {
					LOG(debug) << "Could not parse value \"" << s << "\"";
				}
			}
		}

		if (cpus.empty()) {
			LOG(warning) << "  CPUs could not be parsed!";
		} else {
			std::stringstream sstream;
			sstream << "  CPUS: ";
			for (auto i : cpus) {
				sstream << i << ", ";
			}
			std::string msg = sstream.str();
			msg.pop_back();
			msg.pop_back();
			LOG(debug) << msg;
		}
		return cpus;
	}

Alessio Netti's avatar
Alessio Netti committed
880
881
882
	/**
	 * Adjusts the names of the sensors in generated groups according to the sensorPattern specified in the global
	 * settings. Operates in tandem with the auto-publish feature.
Alessio Netti's avatar
Alessio Netti committed
883
884
	 *
	 * @return		true if successful, false otherwise
Alessio Netti's avatar
Alessio Netti committed
885
	 */
Alessio Netti's avatar
Alessio Netti committed
886
	bool constructSensorNames() {
Alessio Netti's avatar
Alessio Netti committed
887
888
889
		boost::regex sensorReg(SENSOR_PATTERN), groupReg(GROUP_PATTERN);
		boost::cmatch match;
		if(_sensorPattern == "")
Alessio Netti's avatar
Alessio Netti committed
890
			return true;
Alessio Netti's avatar
Alessio Netti committed
891
		else if (!boost::regex_search(_sensorPattern.c_str(), match, sensorReg)) {
Alessio Netti's avatar
Alessio Netti committed
892
893
			LOG(error) << "Invalid sensor naming pattern " << _sensorPattern << ". You must at least include <sensor>!";
			return false;
Alessio Netti's avatar
Alessio Netti committed
894
895
896
897
898
899
900
901
902
903
904
905
		}

		std::string name;
		// Performing auto-publish for sensors
		for(auto& g: _sensorGroups)
			for(auto& s: g->getSensors()) {
					name = _sensorPattern;
					name = boost::regex_replace(name, sensorReg, s->getName());
					name = boost::regex_replace(name, groupReg, g->getGroupName());
					// Setting the auto-publish name back to the sensor
					s->setName(name);
				}
Alessio Netti's avatar
Alessio Netti committed
906
		return true;
Alessio Netti's avatar
Alessio Netti committed
907
908
	}

909
910
	std::string		_entityName;
	std::string		_groupName;
911
	std::string		_baseName;
912
913
914

	std::string 	_cfgPath;
	std::string		_mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
915
	std::string		_sensorPattern;
916
	unsigned int	_cacheInterval;
917
918
	std::vector<SGroupPtr> 	_sensorGroupInterfaces;
	std::vector<SG_Ptr>		_sensorGroups;
919
	std::vector<SEntity*>	_sensorEntitys;
Michael Ott's avatar
Michael Ott committed
920
	sBaseMap_t		_templateSensorBases;
921
922
	sGroupMap_t		_templateSensorGroups;
	sEntityMap_t	_templateSensorEntitys;
923
924
};

925
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */