]> granicus.if.org Git - icinga2/blob - lib/base/configobject.cpp
Hide internal attributes in the API
[icinga2] / lib / base / configobject.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2015 Icinga Development Team (http://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(GetTypeNameV());
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)
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         SetVersion(Utility::GetTime());
348 }
349
350 bool ConfigObject::IsAttributeModified(const String& attr) const
351 {
352         Dictionary::Ptr original_attributes = GetOriginalAttributes();
353
354         if (!original_attributes)
355                 return false;
356
357         return original_attributes->Contains(attr);
358 }
359
360 void ConfigObject::Register(void)
361 {
362         ASSERT(!OwnsLock());
363
364         ConfigType::Ptr dtype = GetType();
365         dtype->RegisterObject(this);
366 }
367
368 void ConfigObject::Unregister(void)
369 {
370         ASSERT(!OwnsLock());
371
372         ConfigType::Ptr dtype = GetType();
373         dtype->UnregisterObject(this);
374 }
375
376 void ConfigObject::Start(void)
377 {
378         ObjectImpl<ConfigObject>::Start();
379
380         ASSERT(!OwnsLock());
381         ObjectLock olock(this);
382
383         SetStartCalled(true);
384 }
385
386 void ConfigObject::Activate(void)
387 {
388         CONTEXT("Activating object '" + GetName() + "' of type '" + GetType()->GetName() + "'");
389
390         ASSERT(!OwnsLock());
391
392         Start();
393
394         ASSERT(GetStartCalled());
395
396         {
397                 ObjectLock olock(this);
398                 ASSERT(!IsActive());
399                 SetActive(true, true);
400         }
401
402         SetAuthority(true);
403
404         NotifyActive();
405 }
406
407 void ConfigObject::Stop(void)
408 {
409         ObjectImpl<ConfigObject>::Stop();
410
411         ASSERT(!OwnsLock());
412         ObjectLock olock(this);
413
414         SetStopCalled(true);
415 }
416
417 void ConfigObject::Deactivate(void)
418 {
419         CONTEXT("Deactivating object '" + GetName() + "' of type '" + GetType()->GetName() + "'");
420
421         ASSERT(!OwnsLock());
422
423         SetAuthority(false);
424
425         {
426                 ObjectLock olock(this);
427
428                 if (!IsActive())
429                         return;
430
431                 SetActive(false, true);
432         }
433
434         Stop();
435
436         ASSERT(GetStopCalled());
437
438         NotifyActive();
439 }
440
441 void ConfigObject::OnConfigLoaded(void)
442 {
443         /* Nothing to do here. */
444 }
445
446 void ConfigObject::OnAllConfigLoaded(void)
447 {
448         /* Nothing to do here. */
449 }
450
451 void ConfigObject::CreateChildObjects(const Type::Ptr& childType)
452 {
453         /* Nothing to do here. */
454 }
455
456 void ConfigObject::OnStateLoaded(void)
457 {
458         /* Nothing to do here. */
459 }
460
461 void ConfigObject::Pause(void)
462 {
463         SetPauseCalled(true);
464 }
465
466 void ConfigObject::Resume(void)
467 {
468         SetResumeCalled(true);
469 }
470
471 void ConfigObject::SetAuthority(bool authority)
472 {
473         if (authority && GetPaused()) {
474                 SetResumeCalled(false);
475                 Resume();
476                 ASSERT(GetResumeCalled());
477                 SetPaused(false);
478         } else if (!authority && !GetPaused()) {
479                 SetPauseCalled(false);
480                 Pause();
481                 ASSERT(GetPauseCalled());
482                 SetPaused(true);
483         }
484 }
485
486 void ConfigObject::DumpObjects(const String& filename, int attributeTypes)
487 {
488         Log(LogInformation, "ConfigObject")
489             << "Dumping program state to file '" << filename << "'";
490
491         String tempFilename = filename + ".tmp";
492
493         std::fstream fp;
494         fp.open(tempFilename.CStr(), std::ios_base::out);
495
496         if (!fp)
497                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not open '" + tempFilename + "' file"));
498
499         StdioStream::Ptr sfp = new StdioStream(&fp, false);
500
501         BOOST_FOREACH(const ConfigType::Ptr& type, ConfigType::GetTypes()) {
502                 BOOST_FOREACH(const ConfigObject::Ptr& object, type->GetObjects()) {
503                         Dictionary::Ptr persistentObject = new Dictionary();
504
505                         persistentObject->Set("type", type->GetName());
506                         persistentObject->Set("name", object->GetName());
507
508                         Dictionary::Ptr update = Serialize(object, attributeTypes);
509
510                         if (!update)
511                                 continue;
512
513                         persistentObject->Set("update", update);
514
515                         String json = JsonEncode(persistentObject);
516
517                         NetString::WriteStringToStream(sfp, json);
518                 }
519         }
520
521         sfp->Close();
522
523         fp.close();
524
525 #ifdef _WIN32
526         _unlink(filename.CStr());
527 #endif /* _WIN32 */
528
529         if (rename(tempFilename.CStr(), filename.CStr()) < 0) {
530                 BOOST_THROW_EXCEPTION(posix_error()
531                     << boost::errinfo_api_function("rename")
532                     << boost::errinfo_errno(errno)
533                     << boost::errinfo_file_name(tempFilename));
534         }
535 }
536
537 void ConfigObject::RestoreObject(const String& message, int attributeTypes)
538 {
539         Dictionary::Ptr persistentObject = JsonDecode(message);
540
541         String type = persistentObject->Get("type");
542
543         ConfigType::Ptr dt = ConfigType::GetByName(type);
544
545         if (!dt)
546                 return;
547
548         String name = persistentObject->Get("name");
549
550         ConfigObject::Ptr object = dt->GetObject(name);
551
552         if (!object)
553                 return;
554
555         ASSERT(!object->IsActive());
556 #ifdef I2_DEBUG
557         Log(LogDebug, "ConfigObject")
558             << "Restoring object '" << name << "' of type '" << type << "'.";
559 #endif /* I2_DEBUG */
560         Dictionary::Ptr update = persistentObject->Get("update");
561         Deserialize(object, update, false, attributeTypes);
562         object->OnStateLoaded();
563         object->SetStateLoaded(true);
564 }
565
566 void ConfigObject::RestoreObjects(const String& filename, int attributeTypes)
567 {
568         if (!Utility::PathExists(filename))
569                 return;
570
571         Log(LogInformation, "ConfigObject")
572             << "Restoring program state from file '" << filename << "'";
573
574         std::fstream fp;
575         fp.open(filename.CStr(), std::ios_base::in);
576
577         StdioStream::Ptr sfp = new StdioStream (&fp, false);
578
579         unsigned long restored = 0;
580
581         WorkQueue upq(25000, Application::GetConcurrency());
582
583         String message;
584         StreamReadContext src;
585         for (;;) {
586                 StreamReadStatus srs = NetString::ReadStringFromStream(sfp, &message, src);
587
588                 if (srs == StatusEof)
589                         break;
590
591                 if (srs != StatusNewItem)
592                         continue;
593
594                 upq.Enqueue(boost::bind(&ConfigObject::RestoreObject, message, attributeTypes));
595                 restored++;
596         }
597
598         sfp->Close();
599
600         upq.Join();
601
602         unsigned long no_state = 0;
603
604         BOOST_FOREACH(const ConfigType::Ptr& type, ConfigType::GetTypes()) {
605                 BOOST_FOREACH(const ConfigObject::Ptr& object, type->GetObjects()) {
606                         if (!object->GetStateLoaded()) {
607                                 object->OnStateLoaded();
608                                 object->SetStateLoaded(true);
609
610                                 no_state++;
611                         }
612                 }
613         }
614
615         Log(LogInformation, "ConfigObject")
616             << "Restored " << restored << " objects. Loaded " << no_state << " new objects without state.";
617 }
618
619 void ConfigObject::StopObjects(void)
620 {
621         BOOST_FOREACH(const ConfigType::Ptr& dt, ConfigType::GetTypes()) {
622                 BOOST_FOREACH(const ConfigObject::Ptr& object, dt->GetObjects()) {
623                         object->Deactivate();
624                 }
625         }
626 }
627
628 void ConfigObject::DumpModifiedAttributes(const boost::function<void(const ConfigObject::Ptr&, const String&, const Value&)>& callback)
629 {
630         BOOST_FOREACH(const ConfigType::Ptr& dt, ConfigType::GetTypes()) {
631                 BOOST_FOREACH(const ConfigObject::Ptr& object, dt->GetObjects()) {
632                         Dictionary::Ptr originalAttributes = object->GetOriginalAttributes();
633
634                         if (!originalAttributes)
635                                 continue;
636
637                         ObjectLock olock(originalAttributes);
638                         BOOST_FOREACH(const Dictionary::Pair& kv, originalAttributes) {
639                                 String key = kv.first;
640
641                                 Type::Ptr type = object->GetReflectionType();
642
643                                 std::vector<String> tokens;
644                                 boost::algorithm::split(tokens, key, boost::is_any_of("."));
645
646                                 String fieldName = tokens[0];
647                                 int fid = type->GetFieldId(fieldName);
648
649                                 Value currentValue = object->GetField(fid);
650                                 Value modifiedValue;
651
652                                 if (tokens.size() > 1) {
653                                         Value current = currentValue;
654
655                                         for (std::vector<String>::size_type i = 1; i < tokens.size() - 1; i++) {
656                                                 if (!current.IsObjectType<Dictionary>())
657                                                         BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
658
659                                                 Dictionary::Ptr dict = current;
660                                                 const String& key = tokens[i];
661
662                                                 if (!dict->Contains(key))
663                                                         break;
664
665                                                 current = dict->Get(key);
666                                         }
667
668                                         if (!current.IsObjectType<Dictionary>())
669                                                 BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary."));
670
671                                         Dictionary::Ptr dict = current;
672                                         const String& key = tokens[tokens.size() - 1];
673
674                                         modifiedValue = dict->Get(key);
675                                 } else
676                                         modifiedValue = currentValue;
677
678                                 callback(object, key, modifiedValue);
679                         }
680                 }
681         }
682
683 }
684
685 ConfigObject::Ptr ConfigObject::GetObject(const String& type, const String& name)
686 {
687         ConfigType::Ptr dtype = ConfigType::GetByName(type);
688         if (!dtype)
689                 return ConfigObject::Ptr();
690         return dtype->GetObject(name);
691 }