The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

ConfiguratorTemplate.h 32.2 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
template <class SBase, class SGroup, class SEntity = nullptr_t>
58
class ConfiguratorTemplate : public ConfiguratorInterface {
59
	//the template shall only be instantiated for classes which derive from SensorBase/SensorGroup
60
	static_assert(std::is_base_of<SensorBase, SBase>::value, "SBase must derive from SensorBase!");
61
	static_assert(std::is_base_of<SensorGroupInterface, SGroup>::value, "SGroup must derive from SensorGroupInterface!");
62

63
protected:
Michael Ott's avatar
Michael Ott committed
64
	typedef std::map<std::string, SBase*> sBaseMap_t;
65
66
	typedef std::map<std::string, SGroup*> sGroupMap_t;
	typedef std::map<std::string, SEntity*> sEntityMap_t;
67

68
69
70
	using SB_Ptr = std::shared_ptr<SBase>;
	using SG_Ptr = std::shared_ptr<SGroup>;

71
72
73
74
75
	const char COMMA = ',';
	const char OPEN_SQBRKET = '[';
	const char CLOSE_SQBRKET = ']';
	const char DASH = '-';

Alessio Netti's avatar
Alessio Netti committed
76
77
	const std::string SENSOR_PATTERN = "(?i)<sensor>";
	const std::string GROUP_PATTERN = "(?i)<group>";
Alessio Netti's avatar
Alessio Netti committed
78

79
public:
80
	ConfiguratorTemplate() :
81
82
83
		_entityName("INVALID"),
		_groupName("INVALID"),
		_baseName("INVALID"),
84
85
		_cfgPath(""),
		_mqttPrefix(""),
Alessio Netti's avatar
Alessio Netti committed
86
		_sensorPattern(""),
87
		_cacheInterval(DEFAULT_CACHE_INTERVAL) {}
88

89
90
	ConfiguratorTemplate(const ConfiguratorTemplate&) = delete;

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

112
113
	ConfiguratorTemplate& operator=(const ConfiguratorTemplate&) = delete;

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

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

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

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

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

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

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

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

Micha Mueller's avatar
Micha Mueller committed
327
		//back to the very beginning
328
		return readConfig(_cfgPath);
329
	}
330

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

352
	  //prints plugin specific configurator attributes and entities if present
353
354
	  printConfiguratorConfig(ll);

355
	  LOG_VAR(ll) << "    Groups:";
356
	  for(auto g : _sensorGroups) {
357
358
359
	    g->SensorGroupInterface::printConfig(ll);
	    g->printConfig(ll);
	    g->SensorGroupTemplate<SBase>::printConfig(ll);
360
361
362
	  }
	}

363
364
365
366
367
368
369
	/**
	 * 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.
	 */
370
	void setGlobalSettings(const pluginSettings_t& pluginSettings) final {
371
		_mqttPrefix = pluginSettings.mqttPrefix;
Alessio Netti's avatar
Alessio Netti committed
372
		_sensorPattern = pluginSettings.sensorPattern;
373
		_cacheInterval = pluginSettings.cacheInterval;
374
375

		derivedSetGlobalSettings(pluginSettings);
376
377
	}

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

387
protected:
388
	void storeSensorGroup(SG_Ptr sGroup) {
389
390
		_sensorGroups.push_back(sGroup);
		_sensorGroupInterfaces.push_back(sGroup);
Micha Mueller's avatar
Micha Mueller committed
391
392
	}

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

	/**
	 * Non-virtual interface method for class-internal use only.
441
442
	 * Reads and sets the common base values of a sensor group, then calls
	 * the corresponding derived function to read in plugin specific values.
443
	 *
444
	 * @param sGroup	The sensor group for which to set the values
445
	 * @param config	A boost property (sub-)tree containing the sensor values
446
	 * @param isTemplate	Is this a template or a regular group?
447
448
449
	 *
	 * @return	True on success, false otherwise
	 */
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
	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.";
				}
465
466
467
			}
		}

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

495
496
497
		//TODO keep debug logging for config?
//		LOG(debug) << "  Interval : " << sGroup.getInterval();
//		LOG(debug) << "  minValues: " << sGroup.getMinValues();
498

499
500
		sensorGroup(sGroup, config);
		return true;
501
502
	}

503
504
505
506
507
508
509
	/**
	 * 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
510
511
	 * @param isTemplate	Indicate if sEntity is a template. If so, also store
	 * 						the corresponding sGroups in the template map
512
513
514
	 *
	 * @return	True on success, false otherwise
	 */
Micha Mueller's avatar
Micha Mueller committed
515
	bool readSensorEntity(SEntity& sEntity, CFG_VAL config, bool isTemplate) {
516
517
518
519
520
521
522
		//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()) {
523
				sEntity = *(it->second);
Micha Mueller's avatar
Micha Mueller committed
524
525
				for(auto g : _templateSensorGroups) {
					if (isEntityOfGroup(*(it->second), *(g.second))) {
526
						SG_Ptr group = std::make_shared<SGroup>(*(g.second));
Micha Mueller's avatar
Micha Mueller committed
527
						setEntityForGroup(sEntity, *group);
528
						storeSensorGroup(group);
Micha Mueller's avatar
Micha Mueller committed
529
530
					}
				}
531
532
533
534
			} else {
				LOG(warning) << "Template " << _entityName << "\"" << def.get().data() << "\" not found! Using standard values.";
			}
		}
Micha Mueller's avatar
Micha Mueller committed
535

536
		sensorEntity(sEntity, config);
Micha Mueller's avatar
Micha Mueller committed
537
538
539
540

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

626
		if(!isTemplate) {
627
			for(const auto& g : _sensorGroups) {
628
629
630
				if(isEntityOfGroup(sEntity, *g)) {
					finalizeGroup(*g);
				}
Micha Mueller's avatar
Micha Mueller committed
631
632
			}
		}
633
		return true;
634
635
	}

Micha Mueller's avatar
Micha Mueller committed
636
	bool readGlobal(CFG_VAL config) {
637
638
639
640
641
642
643
644
645
646
647
		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
648
649
				} else if (boost::iequals(global.first, "sensorpattern")) {
					_sensorPattern = global.second.data();
650
651
				}
			}
652
			global(config.get_child("global"));
653
		}
654
		return true;
655
656
	}

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

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

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

685
686
687
688
	/**
	 * Virtual interface method, responsible for reading plugin-specific sensor
	 * entity values.
	 *
Micha Mueller's avatar
Micha Mueller committed
689
	 * @param s			The sensor entity for which to set the values
690
691
	 * @param config	A boost property (sub-)tree containing the entity values
	 */
Micha Mueller's avatar
Micha Mueller committed
692
	virtual void sensorEntity(SEntity& s, CFG_VAL config) {
693
694
695
		//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";
	}
696

Micha Mueller's avatar
Micha Mueller committed
697
698
699
700
701
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
	/**
	 * 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";
	}

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

746
747
748
749
750
	/**
	 * 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
751
	virtual void global(CFG_VAL config) {}
752

Alessio Netti's avatar
Alessio Netti committed
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
	/**
	 * 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();
	}

	/**
773
774
775
	 * 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
776
	 *
777
778
779
	 * 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
780
781
782
783
784
785
786
787
788
	 *
	 * @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;
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805

		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
806
807
808
809
810
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
	}

	/**
	 * 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
875
876
877
	/**
	 * 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
878
879
	 *
	 * @return		true if successful, false otherwise
Alessio Netti's avatar
Alessio Netti committed
880
	 */
Alessio Netti's avatar
Alessio Netti committed
881
	bool constructSensorNames() {
Alessio Netti's avatar
Alessio Netti committed
882
883
884
		boost::regex sensorReg(SENSOR_PATTERN), groupReg(GROUP_PATTERN);
		boost::cmatch match;
		if(_sensorPattern == "")
Alessio Netti's avatar
Alessio Netti committed
885
			return true;
Alessio Netti's avatar
Alessio Netti committed
886
		else if (!boost::regex_search(_sensorPattern.c_str(), match, sensorReg)) {
Alessio Netti's avatar
Alessio Netti committed
887
888
			LOG(error) << "Invalid sensor naming pattern " << _sensorPattern << ". You must at least include <sensor>!";
			return false;
Alessio Netti's avatar
Alessio Netti committed
889
890
891
892
893
894
895
896
897
898
899
900
		}

		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
901
		return true;
Alessio Netti's avatar
Alessio Netti committed
902
903
	}

904
905
	std::string		_entityName;
	std::string		_groupName;
906
	std::string		_baseName;
907
908
909

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

920
#endif /* SRC_CONFIGURATORTEMPLATE_H_ */