]> granicus.if.org Git - icinga2/blob - lib/base/configobject.cpp
Update copyright headers for 2016
[icinga2] / lib / base / configobject.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2016 Icinga Development Team (https://www.icinga.org/)  *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program is distributed in the hope that it will be useful,            *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "base/configobject.hpp"
21 #include "base/configobject.tcpp"
22 #include "base/configtype.hpp"
23 #include "base/serializer.hpp"
24 #include "base/netstring.hpp"
25 #include "base/json.hpp"
26 #include "base/stdiostream.hpp"
27 #include "base/debug.hpp"
28 #include "base/objectlock.hpp"
29 #include "base/logger.hpp"
30 #include "base/exception.hpp"
31 #include "base/function.hpp"
32 #include "base/initialize.hpp"
33 #include "base/workqueue.hpp"
34 #include "base/context.hpp"
35 #include "base/application.hpp"
36 #include "config/configitem.hpp"
37 #include <fstream>
38 #include <boost/foreach.hpp>
39 #include <boost/algorithm/string/split.hpp>
40 #include <boost/algorithm/string/classification.hpp>
41 #include <boost/exception/errinfo_api_function.hpp>
42 #include <boost/exception/errinfo_errno.hpp>
43 #include <boost/exception/errinfo_file_name.hpp>
44
45 using namespace icinga;
46
47 REGISTER_TYPE_WITH_PROTOTYPE(ConfigObject, ConfigObject::GetPrototype());
48
49 boost::signals2::signal<void (const ConfigObject::Ptr&)> ConfigObject::OnStateChanged;
50
51 ConfigObject::ConfigObject(void)
52 { }
53
54 ConfigType::Ptr ConfigObject::GetType(void) const
55 {
56         return ConfigType::GetByName(GetReflectionType()->GetName());
57 }
58
59 bool ConfigObject::IsActive(void) const
60 {
61         return GetActive();
62 }
63
64 bool ConfigObject::IsPaused(void) const
65 {
66         return GetPaused();
67 }
68
69 void ConfigObject::SetExtension(const String& key, const Value& value)
70 {
71         Dictionary::Ptr extensions = GetExtensions();
72
73         if (!extensions) {
74                 extensions = new Dictionary();
75                 SetExtensions(extensions);
76         }
77
78         extensions->Set(key, value);
79 }
80
81 Value ConfigObject::GetExtension(const String& key)
82 {
83         Dictionary::Ptr extensions = GetExtensions();
84
85         if (!extensions)
86                 return Empty;
87
88         return extensions->Get(key);
89 }
90
91 void ConfigObject::ClearExtension(const String& key)
92 {
93         Dictionary::Ptr extensions = GetExtensions();
94
95         if (!extensions)
96                 return;
97
98         extensions->Remove(key);
99 }
100
101 class ModAttrValidationUtils : public ValidationUtils
102 {
103 public:
104         virtual bool ValidateName(const String& type, const String& name) const override
105         {
106                 ConfigType::Ptr dtype = ConfigType::GetByName(type);
107
108                 if (!dtype)
109                         return false;
110
111                 if (!dtype->GetObject(name))
112                         return false;
113
114                 return true;
115         }
116 };
117
118 void ConfigObject::ModifyAttribute(const String& attr, const Value& value, bool updateVersion)
119 {
120         Dictionary::Ptr original_attributes = GetOriginalAttributes();
121         bool updated_original_attributes = false;
122
123         Type::Ptr type = GetReflectionType();
124
125         std::vector<String> tokens;
126         boost::algorithm::split(tokens, attr, boost::is_any_of("."));
127
128         String fieldName = tokens[0];
129
130         int fid = type->GetFieldId(fieldName);
131         Field field = type->GetFieldInfo(fid);
132
133         if (field.Attributes & FANoUserModify)
134                 BOOST_THROW_EXCEPTION(std::invalid_argument("Attribute cannot be modified."));
135
136         if (field.Attributes & FAConfig) {
137                 if (!original_attributes) {
138                         original_attributes = new Dictionary();
139                         SetOriginalAttributes(original_attributes, true);
140                 }
141         }
142
143         Value oldValue = GetField(fid);
144         Value newValue;
145
146         if (tokens.size() > 1) {
147                 newValue = oldValue.Clone();
148                 Value current = newValue;
149
150                 if (current.IsEmpty()) {
151                         current = new Dictionary();
152                         newValue = current;
153                 }
154
155                 String prefix = tokens[0];
156
157                 for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
158                         if (!current.IsObjectType<Dictionary>())
159                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
160
161                         Dictionary::Ptr dict = current;
162
163                         const String& key = tokens[i];
164                         prefix += "." + key;
165
166                         if (!dict->Contains(key)) {
167                                 current = new Dictionary();
168                                 dict->Set(key, current);
169                         } else {
170                                 current = dict->Get(key);
171                         }
172                 }
173
174                 if (!current.IsObjectType<Dictionary>())
175                         BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
176
177                 Dictionary::Ptr dict = current;
178
179                 const String& key = tokens[tokens.size() - 1];
180                 prefix += "." + key;
181
182                 /* clone it for original attributes */
183                 oldValue = dict->Get(key).Clone();
184
185                 if (field.Attributes & FAConfig) {
186                         updated_original_attributes = true;
187
188                         if (oldValue.IsObjectType<Dictionary>()) {
189                                 Dictionary::Ptr oldDict = oldValue;
190                                 ObjectLock olock(oldDict);
191                                 BOOST_FOREACH(const Dictionary::Pair& kv, oldDict) {
192                                         String key = prefix + "." + kv.first;
193                                         if (!original_attributes->Contains(key))
194                                                 original_attributes->Set(key, kv.second);
195                                 }
196
197                                 /* store the new value as null */
198                                 if (value.IsObjectType<Dictionary>()) {
199                                         Dictionary::Ptr valueDict = value;
200                                         ObjectLock olock(valueDict);
201                                         BOOST_FOREACH(const Dictionary::Pair& kv, valueDict) {
202                                                 String key = attr + "." + kv.first;
203                                                 if (!original_attributes->Contains(key))
204                                                         original_attributes->Set(key, Empty);
205                                         }
206                                 }
207                         } else if (!original_attributes->Contains(attr))
208                                 original_attributes->Set(attr, oldValue);
209                 }
210
211                 dict->Set(key, value);
212         } else {
213                 newValue = value;
214
215                 if (field.Attributes & FAConfig) {
216                         if (!original_attributes->Contains(attr)) {
217                                 updated_original_attributes = true;
218                                 original_attributes->Set(attr, oldValue);
219                         }
220                 }
221         }
222
223         ModAttrValidationUtils utils;
224         ValidateField(fid, newValue, utils);
225
226         SetField(fid, newValue);
227
228         if (updateVersion && (field.Attributes & FAConfig))
229                 SetVersion(Utility::GetTime());
230
231         if (updated_original_attributes)
232                 NotifyOriginalAttributes();
233 }
234
235 void ConfigObject::RestoreAttribute(const String& attr, bool updateVersion)
236 {
237         Type::Ptr type = GetReflectionType();
238
239         std::vector<String> tokens;
240         boost::algorithm::split(tokens, attr, boost::is_any_of("."));
241
242         String fieldName = tokens[0];
243
244         int fid = type->GetFieldId(fieldName);
245         Field field = type->GetFieldInfo(fid);
246
247         Value currentValue = GetField(fid);
248
249         Dictionary::Ptr original_attributes = GetOriginalAttributes();
250
251         if (!original_attributes)
252                 return;
253
254         Value oldValue = original_attributes->Get(attr);
255         Value newValue;
256
257         if (tokens.size() > 1) {
258                 newValue = currentValue.Clone();
259                 Value current = newValue;
260
261                 if (current.IsEmpty())
262                         BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existing object attribute"));
263
264                 String prefix = tokens[0];
265
266                 for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
267                         if (!current.IsObjectType<Dictionary>())
268                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
269
270                         Dictionary::Ptr dict = current;
271
272                         const String& key = tokens[i];
273                         prefix += "." + key;
274
275                         if (!dict->Contains(key))
276                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existing object attribute"));
277
278                         current = dict->Get(key);
279                 }
280
281                 if (!current.IsObjectType<Dictionary>())
282                         BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
283
284                 Dictionary::Ptr dict = current;
285
286                 const String& key = tokens[tokens.size() - 1];
287                 prefix += "." + key;
288
289                 std::vector<String> restoredAttrs;
290
291                 {
292                         ObjectLock olock(original_attributes);
293                         BOOST_FOREACH(const Dictionary::Pair& kv, original_attributes) {
294                                 std::vector<String> originalTokens;
295                                 boost::algorithm::split(originalTokens, kv.first, boost::is_any_of("."));
296
297                                 if (tokens.size() > originalTokens.size())
298                                         continue;
299
300                                 bool match = true;
301                                 for (std::vector<String>::size_type i = 0; i < tokens.size(); i++) {
302                                         if (tokens[i] != originalTokens[i]) {
303                                                 match = false;
304                                                 break;
305                                         }
306                                 }
307
308                                 if (!match)
309                                         continue;
310
311                                 Dictionary::Ptr dict;
312
313                                 if (tokens.size() == originalTokens.size())
314                                         dict = current;
315                                 else {
316                                         Value currentSub = current;
317
318                                         for (std::vector<String>::size_type i = tokens.size() - 1; i < originalTokens.size() - 1; i++) {
319                                                 dict = currentSub;
320                                                 currentSub = dict->Get(originalTokens[i]);
321
322                                                 if (!currentSub.IsObjectType<Dictionary>()) {
323                                                         currentSub = new Dictionary();
324                                                         dict->Set(originalTokens[i], currentSub);
325                                                 }
326                                         }
327
328                                         dict = currentSub;
329                                 }
330
331                                 dict->Set(originalTokens[originalTokens.size() - 1], kv.second);
332                                 restoredAttrs.push_back(kv.first);
333                         }
334                 }
335
336                 BOOST_FOREACH(const String& attr, restoredAttrs)
337                         original_attributes->Remove(attr);
338
339
340         } else {
341                 newValue = oldValue;
342         }
343
344         original_attributes->Remove(attr);
345         SetField(fid, newValue);
346
347         if (updateVersion)
348                 SetVersion(Utility::GetTime());
349 }
350
351 bool ConfigObject::IsAttributeModified(const String& attr) const
352 {
353         Dictionary::Ptr original_attributes = GetOriginalAttributes();
354
355         if (!original_attributes)
356                 return false;
357
358         return original_attributes->Contains(attr);
359 }
360
361 void ConfigObject::Register(void)
362 {
363         ASSERT(!OwnsLock());
364
365         ConfigType::Ptr dtype = GetType();
366         dtype->RegisterObject(this);
367 }
368
369 void ConfigObject::Unregister(void)
370 {
371         ASSERT(!OwnsLock());
372
373         ConfigType::Ptr dtype = GetType();
374         dtype->UnregisterObject(this);
375 }
376
377 void ConfigObject::Start(bool runtimeCreated)
378 {
379         ObjectImpl<ConfigObject>::Start(runtimeCreated);
380
381         ObjectLock olock(this);
382
383         SetStartCalled(true);
384 }
385
386 void ConfigObject::Activate(bool runtimeCreated)
387 {
388         CONTEXT("Activating object '" + GetName() + "' of type '" + GetType()->GetName() + "'");
389
390         Start(runtimeCreated);
391
392         ASSERT(GetStartCalled());
393
394         {
395                 ObjectLock olock(this);
396                 ASSERT(!IsActive());
397                 SetActive(true, true);
398         }
399
400         SetAuthority(true);
401
402         NotifyActive();
403 }
404
405 void ConfigObject::Stop(bool runtimeRemoved)
406 {
407         ObjectImpl<ConfigObject>::Stop(runtimeRemoved);
408
409         ObjectLock olock(this);
410
411         SetStopCalled(true);
412 }
413
414 void ConfigObject::Deactivate(bool runtimeRemoved)
415 {
416         CONTEXT("Deactivating object '" + GetName() + "' of type '" + GetType()->GetName() + "'");
417
418         {
419                 ObjectLock olock(this);
420
421                 if (!IsActive())
422                         return;
423
424                 SetActive(false, true);
425         }
426
427         SetAuthority(false);
428
429         Stop(runtimeRemoved);
430
431         ASSERT(GetStopCalled());
432
433         NotifyActive();
434 }
435
436 void ConfigObject::OnConfigLoaded(void)
437 {
438         /* Nothing to do here. */
439 }
440
441 void ConfigObject::OnAllConfigLoaded(void)
442 {
443         m_Zone = GetObject("Zone", GetZoneName());
444 }
445
446 void ConfigObject::CreateChildObjects(const Type::Ptr& childType)
447 {
448         /* Nothing to do here. */
449 }
450
451 void ConfigObject::OnStateLoaded(void)
452 {
453         /* Nothing to do here. */
454 }
455
456 void ConfigObject::Pause(void)
457 {
458         SetPauseCalled(true);
459 }
460
461 void ConfigObject::Resume(void)
462 {
463         SetResumeCalled(true);
464 }
465
466 void ConfigObject::SetAuthority(bool authority)
467 {
468         if (authority && GetPaused()) {
469                 SetResumeCalled(false);
470                 Resume();
471                 ASSERT(GetResumeCalled());
472                 SetPaused(false);
473         } else if (!authority && !GetPaused()) {
474                 SetPaused(true);
475                 SetPauseCalled(false);
476                 Pause();
477                 ASSERT(GetPauseCalled());
478         }
479 }
480
481 void ConfigObject::DumpObjects(const String& filename, int attributeTypes)
482 {
483         Log(LogInformation, "ConfigObject")
484             << "Dumping program state to file '" << filename << "'";
485
486         String tempFilename = filename + ".tmp";
487
488         std::fstream fp;
489         fp.open(tempFilename.CStr(), std::ios_base::out);
490
491         if (!fp)
492                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open '" + tempFilename + "' file"));
493
494         StdioStream::Ptr sfp = new StdioStream(&fp, false);
495
496         BOOST_FOREACH(const ConfigType::Ptr& type, ConfigType::GetTypes()) {
497                 BOOST_FOREACH(const ConfigObject::Ptr& object, type->GetObjects()) {
498                         Dictionary::Ptr persistentObject = new Dictionary();
499
500                         persistentObject->Set("type", type->GetName());
501                         persistentObject->Set("name", object->GetName());
502
503                         Dictionary::Ptr update = Serialize(object, attributeTypes);
504
505                         if (!update)
506                                 continue;
507
508                         persistentObject->Set("update", update);
509
510                         String json = JsonEncode(persistentObject);
511
512                         NetString::WriteStringToStream(sfp, json);
513                 }
514         }
515
516         sfp->Close();
517
518         fp.close();
519
520 #ifdef _WIN32
521         _unlink(filename.CStr());
522 #endif /* _WIN32 */
523
524         if (rename(tempFilename.CStr(), filename.CStr()) < 0) {
525                 BOOST_THROW_EXCEPTION(posix_error()
526                     << boost::errinfo_api_function("rename")
527                     << boost::errinfo_errno(errno)
528                     << boost::errinfo_file_name(tempFilename));
529         }
530 }
531
532 void ConfigObject::RestoreObject(const String& message, int attributeTypes)
533 {
534         Dictionary::Ptr persistentObject = JsonDecode(message);
535
536         String type = persistentObject->Get("type");
537
538         ConfigType::Ptr dt = ConfigType::GetByName(type);
539
540         if (!dt)
541                 return;
542
543         String name = persistentObject->Get("name");
544
545         ConfigObject::Ptr object = dt->GetObject(name);
546
547         if (!object)
548                 return;
549
550         ASSERT(!object->IsActive());
551 #ifdef I2_DEBUG
552         Log(LogDebug, "ConfigObject")
553             << "Restoring object '" << name << "' of type '" << type << "'.";
554 #endif /* I2_DEBUG */
555         Dictionary::Ptr update = persistentObject->Get("update");
556         Deserialize(object, update, false, attributeTypes);
557         object->OnStateLoaded();
558         object->SetStateLoaded(true);
559 }
560
561 void ConfigObject::RestoreObjects(const String& filename, int attributeTypes)
562 {
563         if (!Utility::PathExists(filename))
564                 return;
565
566         Log(LogInformation, "ConfigObject")
567             << "Restoring program state from file '" << filename << "'";
568
569         std::fstream fp;
570         fp.open(filename.CStr(), std::ios_base::in);
571
572         StdioStream::Ptr sfp = new StdioStream (&fp, false);
573
574         unsigned long restored = 0;
575
576         WorkQueue upq(25000, Application::GetConcurrency());
577
578         String message;
579         StreamReadContext src;
580         for (;;) {
581                 StreamReadStatus srs = NetString::ReadStringFromStream(sfp, &message, src);
582
583                 if (srs == StatusEof)
584                         break;
585
586                 if (srs != StatusNewItem)
587                         continue;
588
589                 upq.Enqueue(boost::bind(&ConfigObject::RestoreObject, message, attributeTypes));
590                 restored++;
591         }
592
593         sfp->Close();
594
595         upq.Join();
596
597         unsigned long no_state = 0;
598
599         BOOST_FOREACH(const ConfigType::Ptr& type, ConfigType::GetTypes()) {
600                 BOOST_FOREACH(const ConfigObject::Ptr& object, type->GetObjects()) {
601                         if (!object->GetStateLoaded()) {
602                                 object->OnStateLoaded();
603                                 object->SetStateLoaded(true);
604
605                                 no_state++;
606                         }
607                 }
608         }
609
610         Log(LogInformation, "ConfigObject")
611             << "Restored " << restored << " objects. Loaded " << no_state << " new objects without state.";
612 }
613
614 void ConfigObject::StopObjects(void)
615 {
616         BOOST_FOREACH(const ConfigType::Ptr& dt, ConfigType::GetTypes()) {
617                 BOOST_FOREACH(const ConfigObject::Ptr& object, dt->GetObjects()) {
618                         object->Deactivate();
619                 }
620         }
621 }
622
623 void ConfigObject::DumpModifiedAttributes(const boost::function<void(const ConfigObject::Ptr&, const String&, const Value&)>& callback)
624 {
625         BOOST_FOREACH(const ConfigType::Ptr& dt, ConfigType::GetTypes()) {
626                 BOOST_FOREACH(const ConfigObject::Ptr& object, dt->GetObjects()) {
627                         Dictionary::Ptr originalAttributes = object->GetOriginalAttributes();
628
629                         if (!originalAttributes)
630                                 continue;
631
632                         ObjectLock olock(originalAttributes);
633                         BOOST_FOREACH(const Dictionary::Pair& kv, originalAttributes) {
634                                 String key = kv.first;
635
636                                 Type::Ptr type = object->GetReflectionType();
637
638                                 std::vector<String> tokens;
639                                 boost::algorithm::split(tokens, key, boost::is_any_of("."));
640
641                                 String fieldName = tokens[0];
642                                 int fid = type->GetFieldId(fieldName);
643
644                                 Value currentValue = object->GetField(fid);
645                                 Value modifiedValue;
646
647                                 if (tokens.size() > 1) {
648                                         Value current = currentValue;
649
650                                         for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
651                                                 if (!current.IsObjectType<Dictionary>())
652                                                         BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
653
654                                                 Dictionary::Ptr dict = current;
655                                                 const String& key = tokens[i];
656
657                                                 if (!dict->Contains(key))
658                                                         break;
659
660                                                 current = dict->Get(key);
661                                         }
662
663                                         if (!current.IsObjectType<Dictionary>())
664                                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
665
666                                         Dictionary::Ptr dict = current;
667                                         const String& key = tokens[tokens.size() - 1];
668
669                                         modifiedValue = dict->Get(key);
670                                 } else
671                                         modifiedValue = currentValue;
672
673                                 callback(object, key, modifiedValue);
674                         }
675                 }
676         }
677
678 }
679
680 ConfigObject::Ptr ConfigObject::GetObject(const String& type, const String& name)
681 {
682         ConfigType::Ptr dtype = ConfigType::GetByName(type);
683         if (!dtype)
684                 return ConfigObject::Ptr();
685         return dtype->GetObject(name);
686 }
687
688 ConfigObject::Ptr ConfigObject::GetZone(void) const
689 {
690         return m_Zone;
691 }