]> granicus.if.org Git - icinga2/blob - lib/config/configitem.cpp
Remove deprecated functions
[icinga2] / lib / config / configitem.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 "config/configitem.hpp"
21 #include "config/configcompilercontext.hpp"
22 #include "config/applyrule.hpp"
23 #include "config/objectrule.hpp"
24 #include "base/application.hpp"
25 #include "base/configtype.hpp"
26 #include "base/objectlock.hpp"
27 #include "base/convert.hpp"
28 #include "base/logger.hpp"
29 #include "base/debug.hpp"
30 #include "base/workqueue.hpp"
31 #include "base/exception.hpp"
32 #include "base/stdiostream.hpp"
33 #include "base/netstring.hpp"
34 #include "base/serializer.hpp"
35 #include "base/json.hpp"
36 #include "base/exception.hpp"
37 #include "base/function.hpp"
38 #include <sstream>
39 #include <fstream>
40 #include <boost/foreach.hpp>
41
42 using namespace icinga;
43
44 boost::mutex ConfigItem::m_Mutex;
45 ConfigItem::TypeMap ConfigItem::m_Items;
46 ConfigItem::ItemList ConfigItem::m_UnnamedItems;
47 ConfigItem::IgnoredItemList ConfigItem::m_IgnoredItems;
48
49 REGISTER_SCRIPTFUNCTION_NS(Internal, run_with_activation_context, &ConfigItem::RunWithActivationContext);
50
51 /**
52  * Constructor for the ConfigItem class.
53  *
54  * @param type The object type.
55  * @param name The name of the item.
56  * @param unit The unit of the item.
57  * @param abstract Whether the item is a template.
58  * @param exprl Expression list for the item.
59  * @param debuginfo Debug information.
60  */
61 ConfigItem::ConfigItem(const String& type, const String& name,
62     bool abstract, const boost::shared_ptr<Expression>& exprl,
63     const boost::shared_ptr<Expression>& filter, bool ignoreOnError,
64     const DebugInfo& debuginfo, const Dictionary::Ptr& scope,
65     const String& zone, const String& package)
66         : m_Type(type), m_Name(name), m_Abstract(abstract),
67           m_Expression(exprl), m_Filter(filter), m_IgnoreOnError(ignoreOnError),
68           m_DebugInfo(debuginfo), m_Scope(scope), m_Zone(zone),
69           m_Package(package)
70 {
71 }
72
73 /**
74  * Retrieves the type of the configuration item.
75  *
76  * @returns The type.
77  */
78 String ConfigItem::GetType(void) const
79 {
80         return m_Type;
81 }
82
83 /**
84  * Retrieves the name of the configuration item.
85  *
86  * @returns The name.
87  */
88 String ConfigItem::GetName(void) const
89 {
90         return m_Name;
91 }
92
93 /**
94  * Checks whether the item is abstract.
95  *
96  * @returns true if the item is abstract, false otherwise.
97  */
98 bool ConfigItem::IsAbstract(void) const
99 {
100         return m_Abstract;
101 }
102
103 /**
104  * Retrieves the debug information for the configuration item.
105  *
106  * @returns The debug information.
107  */
108 DebugInfo ConfigItem::GetDebugInfo(void) const
109 {
110         return m_DebugInfo;
111 }
112
113 Dictionary::Ptr ConfigItem::GetScope(void) const
114 {
115         return m_Scope;
116 }
117
118 ConfigObject::Ptr ConfigItem::GetObject(void) const
119 {
120         return m_Object;
121 }
122
123 /**
124  * Retrieves the expression list for the configuration item.
125  *
126  * @returns The expression list.
127  */
128 boost::shared_ptr<Expression> ConfigItem::GetExpression(void) const
129 {
130         return m_Expression;
131 }
132
133 /**
134 * Retrieves the object filter for the configuration item.
135 *
136 * @returns The filter expression.
137 */
138 boost::shared_ptr<Expression> ConfigItem::GetFilter(void) const
139 {
140         return m_Filter;
141 }
142
143 class DefaultValidationUtils : public ValidationUtils
144 {
145 public:
146         virtual bool ValidateName(const String& type, const String& name) const override
147         {
148                 ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(type, name);
149
150                 if (!item || (item && item->IsAbstract()))
151                         return false;
152
153                 return true;
154         }
155 };
156
157 /**
158  * Commits the configuration item by creating a ConfigObject
159  * object.
160  *
161  * @returns The ConfigObject that was created/updated.
162  */
163 ConfigObject::Ptr ConfigItem::Commit(bool discard)
164 {
165 #ifdef I2_DEBUG
166         Log(LogDebug, "ConfigItem")
167             << "Commit called for ConfigItem Type=" << GetType() << ", Name=" << GetName();
168 #endif /* I2_DEBUG */
169
170         /* Make sure the type is valid. */
171         Type::Ptr type = Type::GetByName(GetType());
172         if (!type || !ConfigObject::TypeInstance->IsAssignableFrom(type))
173                 BOOST_THROW_EXCEPTION(ScriptError("Type '" + GetType() + "' does not exist.", m_DebugInfo));
174
175         if (IsAbstract())
176                 return ConfigObject::Ptr();
177
178         ConfigObject::Ptr dobj = static_pointer_cast<ConfigObject>(type->Instantiate(std::vector<Value>()));
179
180         dobj->SetDebugInfo(m_DebugInfo);
181         dobj->SetZoneName(m_Zone);
182         dobj->SetPackage(m_Package);
183         dobj->SetName(m_Name);
184
185         DebugHint debugHints;
186
187         ScriptFrame frame(dobj);
188         if (m_Scope)
189                 m_Scope->CopyTo(frame.Locals);
190         try {
191                 m_Expression->Evaluate(frame, &debugHints);
192         } catch (const std::exception& ex) {
193                 if (m_IgnoreOnError) {
194                         Log(LogNotice, "ConfigObject")
195                             << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex);
196
197                         {
198                                 boost::mutex::scoped_lock lock(m_Mutex);
199                                 m_IgnoredItems.push_back(m_DebugInfo.Path);
200                         }
201
202                         return ConfigObject::Ptr();
203                 }
204
205                 throw;
206         }
207
208         if (discard)
209                 m_Expression.reset();
210
211         String item_name;
212         String short_name = dobj->GetShortName();
213
214         if (!short_name.IsEmpty()) {
215                 item_name = short_name;
216                 dobj->SetName(short_name);
217         } else
218                 item_name = m_Name;
219
220         String name = item_name;
221
222         NameComposer *nc = dynamic_cast<NameComposer *>(type.get());
223
224         if (nc) {
225                 if (name.IsEmpty())
226                         BOOST_THROW_EXCEPTION(ScriptError("Object name must not be empty.", m_DebugInfo));
227
228                 name = nc->MakeName(name, dobj);
229
230                 if (name.IsEmpty())
231                         BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine name for object"));
232         }
233
234         if (name != item_name)
235                 dobj->SetShortName(item_name);
236
237         dobj->SetName(name);
238
239         Dictionary::Ptr dhint = debugHints.ToDictionary();
240
241         try {
242                 DefaultValidationUtils utils;
243                 dobj->Validate(FAConfig, utils);
244         } catch (ValidationError& ex) {
245                 if (m_IgnoreOnError) {
246                         Log(LogNotice, "ConfigObject")
247                             << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex);
248
249                         {
250                                 boost::mutex::scoped_lock lock(m_Mutex);
251                                 m_IgnoredItems.push_back(m_DebugInfo.Path);
252                         }
253
254                         return ConfigObject::Ptr();
255                 }
256
257                 ex.SetDebugHint(dhint);
258                 throw;
259         }
260
261         try {
262                 dobj->OnConfigLoaded();
263         } catch (const std::exception& ex) {
264                 if (m_IgnoreOnError) {
265                         Log(LogNotice, "ConfigObject")
266                             << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex);
267
268                         {
269                                 boost::mutex::scoped_lock lock(m_Mutex);
270                                 m_IgnoredItems.push_back(m_DebugInfo.Path);
271                         }
272
273                         return ConfigObject::Ptr();
274                 }
275
276                 throw;
277         }
278
279         Dictionary::Ptr persistentItem = new Dictionary();
280
281         persistentItem->Set("type", GetType());
282         persistentItem->Set("name", GetName());
283         persistentItem->Set("properties", Serialize(dobj, FAConfig));
284         persistentItem->Set("debug_hints", dhint);
285
286         Array::Ptr di = new Array();
287         di->Add(m_DebugInfo.Path);
288         di->Add(m_DebugInfo.FirstLine);
289         di->Add(m_DebugInfo.FirstColumn);
290         di->Add(m_DebugInfo.LastLine);
291         di->Add(m_DebugInfo.LastColumn);
292         persistentItem->Set("debug_info", di);
293
294         ConfigCompilerContext::GetInstance()->WriteObject(persistentItem);
295         persistentItem.reset();
296
297         dhint.reset();
298
299         dobj->Register();
300
301         m_Object = dobj;
302
303         return dobj;
304 }
305
306 /**
307  * Registers the configuration item.
308  */
309 void ConfigItem::Register(void)
310 {
311         Type::Ptr type = Type::GetByName(m_Type);
312
313         m_ActivationContext = ActivationContext::GetCurrentContext();
314
315         boost::mutex::scoped_lock lock(m_Mutex);
316
317         /* If this is a non-abstract object with a composite name
318          * we register it in m_UnnamedItems instead of m_Items. */
319         if (!m_Abstract && dynamic_cast<NameComposer *>(type.get()))
320                 m_UnnamedItems.push_back(this);
321         else {
322                 ItemMap::const_iterator it = m_Items[m_Type].find(m_Name);
323
324                 if (it != m_Items[m_Type].end()) {
325                         std::ostringstream msgbuf;
326                         msgbuf << "A configuration item of type '" << GetType()
327                                << "' and name '" << GetName() << "' already exists ("
328                                << it->second->GetDebugInfo() << "), new declaration: " << GetDebugInfo();
329                         BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str()));
330                 }
331
332                 m_Items[m_Type][m_Name] = this;
333         }
334 }
335
336 /**
337  * Unregisters the configuration item.
338  */
339 void ConfigItem::Unregister(void)
340 {
341         if (m_Object) {
342                 m_Object->Unregister();
343                 m_Object.reset();
344         }
345
346         boost::mutex::scoped_lock lock(m_Mutex);
347         m_UnnamedItems.erase(std::remove(m_UnnamedItems.begin(), m_UnnamedItems.end(), this), m_UnnamedItems.end());
348         m_Items[m_Type].erase(m_Name);
349 }
350
351 /**
352  * Retrieves a configuration item by type and name.
353  *
354  * @param type The type of the ConfigItem that is to be looked up.
355  * @param name The name of the ConfigItem that is to be looked up.
356  * @returns The configuration item.
357  */
358 ConfigItem::Ptr ConfigItem::GetByTypeAndName(const String& type, const String& name)
359 {
360         boost::mutex::scoped_lock lock(m_Mutex);
361
362         ConfigItem::TypeMap::const_iterator it = m_Items.find(type);
363
364         if (it == m_Items.end())
365                 return ConfigItem::Ptr();
366
367         ConfigItem::ItemMap::const_iterator it2 = it->second.find(name);
368
369         if (it2 == it->second.end())
370                 return ConfigItem::Ptr();
371
372         return it2->second;
373 }
374
375 void ConfigItem::OnAllConfigLoadedHelper(void)
376 {
377         try {
378                 m_Object->OnAllConfigLoaded();
379         } catch (const std::exception& ex) {
380                 if (m_IgnoreOnError) {
381                         Log(LogNotice, "ConfigObject")
382                             << "Ignoring config object '" << m_Name << "' of type '" << m_Type << "' due to errors: " << DiagnosticInformation(ex);
383
384                         Unregister();
385
386                         {
387                                 boost::mutex::scoped_lock lock(m_Mutex);
388                                 m_IgnoredItems.push_back(m_DebugInfo.Path);
389                         }
390
391                         return;
392                 }
393
394                 throw;
395         }
396 }
397
398 void ConfigItem::CreateChildObjectsHelper(const Type::Ptr& type)
399 {
400         ActivationScope ascope(m_ActivationContext);
401         m_Object->CreateChildObjects(type);
402 }
403
404 bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems)
405 {
406         typedef std::pair<ConfigItem::Ptr, bool> ItemPair;
407         std::vector<ItemPair> items;
408
409         {
410                 boost::mutex::scoped_lock lock(m_Mutex);
411
412                 BOOST_FOREACH(const TypeMap::value_type& kv, m_Items) {
413                         BOOST_FOREACH(const ItemMap::value_type& kv2, kv.second) {
414                                 if (kv2.second->m_Abstract || kv2.second->m_Object)
415                                         continue;
416
417                                 if (kv2.second->m_ActivationContext != context)
418                                         continue;
419
420                                 items.push_back(std::make_pair(kv2.second, false));
421                         }
422                 }
423
424                 ItemList newUnnamedItems;
425
426                 BOOST_FOREACH(const ConfigItem::Ptr& item, m_UnnamedItems) {
427                         if (item->m_ActivationContext != context) {
428                                 newUnnamedItems.push_back(item);
429                                 continue;
430                         }
431
432                         if (item->m_Abstract || item->m_Object)
433                                 continue;
434
435                         items.push_back(std::make_pair(item, true));
436                 }
437
438                 m_UnnamedItems.swap(newUnnamedItems);
439         }
440
441         if (items.empty())
442                 return true;
443
444         BOOST_FOREACH(const ItemPair& ip, items) {
445                 newItems.push_back(ip.first);
446                 upq.Enqueue(boost::bind(&ConfigItem::Commit, ip.first, ip.second));
447         }
448
449         upq.Join();
450
451         if (upq.HasExceptions())
452                 return false;
453
454         std::set<String> types;
455
456         BOOST_FOREACH(const Type::Ptr& type, Type::GetAllTypes()) {
457                 if (ConfigObject::TypeInstance->IsAssignableFrom(type))
458                         types.insert(type->GetName());
459         }
460
461         std::set<String> completed_types;
462
463         while (types.size() != completed_types.size()) {
464                 BOOST_FOREACH(const String& type, types) {
465                         if (completed_types.find(type) != completed_types.end())
466                                 continue;
467
468                         Type::Ptr ptype = Type::GetByName(type);
469                         bool unresolved_dep = false;
470
471                         /* skip this type (for now) if there are unresolved load dependencies */
472                         BOOST_FOREACH(const String& loadDep, ptype->GetLoadDependencies()) {
473                                 if (types.find(loadDep) != types.end() && completed_types.find(loadDep) == completed_types.end()) {
474                                         unresolved_dep = true;
475                                         break;
476                                 }
477                         }
478
479                         if (unresolved_dep)
480                                 continue;
481
482                         BOOST_FOREACH(const ItemPair& ip, items) {
483                                 const ConfigItem::Ptr& item = ip.first;
484
485                                 if (!item->m_Object)
486                                         continue;
487
488                                 if (item->m_Type == type)
489                                         upq.Enqueue(boost::bind(&ConfigItem::OnAllConfigLoadedHelper, item));
490                         }
491
492                         completed_types.insert(type);
493
494                         upq.Join();
495
496                         if (upq.HasExceptions())
497                                 return false;
498
499                         BOOST_FOREACH(const String& loadDep, ptype->GetLoadDependencies()) {
500                                 BOOST_FOREACH(const ItemPair& ip, items) {
501                                         const ConfigItem::Ptr& item = ip.first;
502
503                                         if (!item->m_Object)
504                                                 continue;
505
506                                         if (item->m_Type == loadDep)
507                                                 upq.Enqueue(boost::bind(&ConfigItem::CreateChildObjectsHelper, item, ptype));
508                                 }
509                         }
510
511                         upq.Join();
512
513                         if (upq.HasExceptions())
514                                 return false;
515
516                         if (!CommitNewItems(context, upq, newItems))
517                                 return false;
518                 }
519         }
520
521         return true;
522 }
523
524 bool ConfigItem::CommitItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems, bool silent)
525 {
526         if (!silent)
527                 Log(LogInformation, "ConfigItem", "Committing config item(s).");
528
529         if (!CommitNewItems(context, upq, newItems)) {
530                 upq.ReportExceptions("config");
531
532                 BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
533                         item->Unregister();
534                 }
535
536                 return false;
537         }
538
539         ApplyRule::CheckMatches();
540
541         if (!silent) {
542                 /* log stats for external parsers */
543                 typedef std::map<Type::Ptr, int> ItemCountMap;
544                 ItemCountMap itemCounts;
545                 BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
546                         if (!item->m_Object)
547                                 continue;
548
549                         itemCounts[item->m_Object->GetReflectionType()]++;
550                 }
551
552                 BOOST_FOREACH(const ItemCountMap::value_type& kv, itemCounts) {
553                         Log(LogInformation, "ConfigItem")
554                             << "Instantiated " << kv.second << " " << (kv.second != 1 ? kv.first->GetPluralName() : kv.first->GetName()) << ".";
555                 }
556         }
557
558         return true;
559 }
560
561 bool ConfigItem::ActivateItems(WorkQueue& upq, const std::vector<ConfigItem::Ptr>& newItems, bool runtimeCreated, bool silent)
562 {
563         static boost::mutex mtx;
564         boost::mutex::scoped_lock lock(mtx);
565
566         if (!silent)
567                 Log(LogInformation, "ConfigItem", "Triggering Start signal for config items");
568
569         BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
570                 if (!item->m_Object)
571                         continue;
572
573                 ConfigObject::Ptr object = item->m_Object;
574
575                 if (object->IsActive())
576                         continue;
577
578 #ifdef I2_DEBUG
579                 Log(LogDebug, "ConfigItem")
580                     << "Activating object '" << object->GetName() << "' of type '" << object->GetReflectionType()->GetName() << "'";
581 #endif /* I2_DEBUG */
582                 upq.Enqueue(boost::bind(&ConfigObject::Activate, object, runtimeCreated));
583         }
584
585         upq.Join();
586
587         if (upq.HasExceptions()) {
588                 upq.ReportExceptions("ConfigItem");
589                 return false;
590         }
591
592 #ifdef I2_DEBUG
593         BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
594                 ConfigObject::Ptr object = item->m_Object;
595
596                 if (!object)
597                         continue;
598
599                 ASSERT(object && object->IsActive());
600         }
601 #endif /* I2_DEBUG */
602
603         if (!silent)
604                 Log(LogInformation, "ConfigItem", "Activated all objects.");
605
606         return true;
607 }
608
609 bool ConfigItem::RunWithActivationContext(const Function::Ptr& function)
610 {
611         ActivationScope scope;
612
613         if (!function)
614                 BOOST_THROW_EXCEPTION(ScriptError("'function' argument must not be null."));
615
616         function->Invoke();
617
618         WorkQueue upq(25000, Application::GetConcurrency());
619         upq.SetName("ConfigItem::RunWithActivationContext");
620
621         std::vector<ConfigItem::Ptr> newItems;
622
623         if (!CommitItems(scope.GetContext(), upq, newItems, true))
624                 return false;
625
626         if (!ActivateItems(upq, newItems, false, true))
627                 return false;
628
629         return true;
630 }
631
632 std::vector<ConfigItem::Ptr> ConfigItem::GetItems(const String& type)
633 {
634         std::vector<ConfigItem::Ptr> items;
635
636         boost::mutex::scoped_lock lock(m_Mutex);
637
638         TypeMap::const_iterator it = m_Items.find(type);
639
640         if (it == m_Items.end())
641                 return items;
642
643         BOOST_FOREACH(const ItemMap::value_type& kv, it->second)
644         {
645                 items.push_back(kv.second);
646         }
647
648         return items;
649 }
650
651 void ConfigItem::RemoveIgnoredItems(const String& allowedConfigPath)
652 {
653         boost::mutex::scoped_lock lock(m_Mutex);
654
655         BOOST_FOREACH(const String& path, m_IgnoredItems) {
656                 if (path.Find(allowedConfigPath) == String::NPos)
657                         continue;
658
659                 Log(LogNotice, "ConfigItem")
660                     << "Removing ignored item path '" << path << "'.";
661
662                 if (unlink(path.CStr()) < 0) {
663                         BOOST_THROW_EXCEPTION(posix_error()
664                             << boost::errinfo_api_function("unlink")
665                             << boost::errinfo_errno(errno)
666                             << boost::errinfo_file_name(path));
667                 }
668         }
669
670         m_IgnoredItems.clear();
671 }