1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "config/configitem.hpp"
4 #include "config/configcompilercontext.hpp"
5 #include "config/applyrule.hpp"
6 #include "config/objectrule.hpp"
7 #include "config/configcompiler.hpp"
8 #include "base/application.hpp"
9 #include "base/configtype.hpp"
10 #include "base/objectlock.hpp"
11 #include "base/convert.hpp"
12 #include "base/logger.hpp"
13 #include "base/debug.hpp"
14 #include "base/workqueue.hpp"
15 #include "base/exception.hpp"
16 #include "base/stdiostream.hpp"
17 #include "base/netstring.hpp"
18 #include "base/serializer.hpp"
19 #include "base/json.hpp"
20 #include "base/exception.hpp"
21 #include "base/function.hpp"
22 #include <boost/algorithm/string/join.hpp>
28 using namespace icinga;
30 boost::mutex ConfigItem::m_Mutex;
31 ConfigItem::TypeMap ConfigItem::m_Items;
32 ConfigItem::TypeMap ConfigItem::m_DefaultTemplates;
33 ConfigItem::ItemList ConfigItem::m_UnnamedItems;
34 ConfigItem::IgnoredItemList ConfigItem::m_IgnoredItems;
36 REGISTER_FUNCTION(Internal, run_with_activation_context, &ConfigItem::RunWithActivationContext, "func");
39 * Constructor for the ConfigItem class.
41 * @param type The object type.
42 * @param name The name of the item.
43 * @param unit The unit of the item.
44 * @param abstract Whether the item is a template.
45 * @param exprl Expression list for the item.
46 * @param debuginfo Debug information.
48 ConfigItem::ConfigItem(Type::Ptr type, String name,
49 bool abstract, std::shared_ptr<Expression> exprl,
50 std::shared_ptr<Expression> filter, bool defaultTmpl, bool ignoreOnError,
51 DebugInfo debuginfo, Dictionary::Ptr scope,
52 String zone, String package)
53 : m_Type(std::move(type)), m_Name(std::move(name)), m_Abstract(abstract),
54 m_Expression(std::move(exprl)), m_Filter(std::move(filter)),
55 m_DefaultTmpl(defaultTmpl), m_IgnoreOnError(ignoreOnError),
56 m_DebugInfo(std::move(debuginfo)), m_Scope(std::move(scope)), m_Zone(std::move(zone)),
57 m_Package(std::move(package))
62 * Retrieves the type of the configuration item.
66 Type::Ptr ConfigItem::GetType() const
72 * Retrieves the name of the configuration item.
76 String ConfigItem::GetName() const
82 * Checks whether the item is abstract.
84 * @returns true if the item is abstract, false otherwise.
86 bool ConfigItem::IsAbstract() const
91 bool ConfigItem::IsDefaultTemplate() const
96 bool ConfigItem::IsIgnoreOnError() const
98 return m_IgnoreOnError;
102 * Retrieves the debug information for the configuration item.
104 * @returns The debug information.
106 DebugInfo ConfigItem::GetDebugInfo() const
111 Dictionary::Ptr ConfigItem::GetScope() const
116 ConfigObject::Ptr ConfigItem::GetObject() const
122 * Retrieves the expression list for the configuration item.
124 * @returns The expression list.
126 std::shared_ptr<Expression> ConfigItem::GetExpression() const
132 * Retrieves the object filter for the configuration item.
134 * @returns The filter expression.
136 std::shared_ptr<Expression> ConfigItem::GetFilter() const
141 class DefaultValidationUtils final : public ValidationUtils
144 bool ValidateName(const String& type, const String& name) const override
146 ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(Type::GetByName(type), name);
148 if (!item || (item && item->IsAbstract()))
156 * Commits the configuration item by creating a ConfigObject
159 * @returns The ConfigObject that was created/updated.
161 ConfigObject::Ptr ConfigItem::Commit(bool discard)
163 Type::Ptr type = GetType();
166 Log(LogDebug, "ConfigItem")
167 << "Commit called for ConfigItem Type=" << type->GetName() << ", Name=" << GetName();
168 #endif /* I2_DEBUG */
170 /* Make sure the type is valid. */
171 if (!type || !ConfigObject::TypeInstance->IsAssignableFrom(type))
172 BOOST_THROW_EXCEPTION(ScriptError("Type '" + type->GetName() + "' does not exist.", m_DebugInfo));
177 ConfigObject::Ptr dobj = static_pointer_cast<ConfigObject>(type->Instantiate(std::vector<Value>()));
179 dobj->SetDebugInfo(m_DebugInfo);
180 dobj->SetZoneName(m_Zone);
181 dobj->SetPackage(m_Package);
182 dobj->SetName(m_Name);
184 DebugHint debugHints;
186 ScriptFrame frame(true, dobj);
188 m_Scope->CopyTo(frame.Locals);
190 m_Expression->Evaluate(frame, &debugHints);
191 } catch (const std::exception& ex) {
192 if (m_IgnoreOnError) {
193 Log(LogNotice, "ConfigObject")
194 << "Ignoring config object '" << m_Name << "' of type '" << type->GetName() << "' due to errors: " << DiagnosticInformation(ex);
197 boost::mutex::scoped_lock lock(m_Mutex);
198 m_IgnoredItems.push_back(m_DebugInfo.Path);
208 m_Expression.reset();
211 String short_name = dobj->GetShortName();
213 if (!short_name.IsEmpty()) {
214 item_name = short_name;
215 dobj->SetName(short_name);
219 String name = item_name;
221 auto *nc = dynamic_cast<NameComposer *>(type.get());
225 BOOST_THROW_EXCEPTION(ScriptError("Object name must not be empty.", m_DebugInfo));
227 name = nc->MakeName(name, dobj);
230 BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine name for object"));
233 if (name != item_name)
234 dobj->SetShortName(item_name);
238 Dictionary::Ptr dhint = debugHints.ToDictionary();
241 DefaultValidationUtils utils;
242 dobj->Validate(FAConfig, utils);
243 } catch (ValidationError& ex) {
244 if (m_IgnoreOnError) {
245 Log(LogNotice, "ConfigObject")
246 << "Ignoring config object '" << m_Name << "' of type '" << type->GetName() << "' due to errors: " << DiagnosticInformation(ex);
249 boost::mutex::scoped_lock lock(m_Mutex);
250 m_IgnoredItems.push_back(m_DebugInfo.Path);
256 ex.SetDebugHint(dhint);
261 dobj->OnConfigLoaded();
262 } catch (const std::exception& ex) {
263 if (m_IgnoreOnError) {
264 Log(LogNotice, "ConfigObject")
265 << "Ignoring config object '" << m_Name << "' of type '" << m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex);
268 boost::mutex::scoped_lock lock(m_Mutex);
269 m_IgnoredItems.push_back(m_DebugInfo.Path);
278 Value serializedObject;
281 serializedObject = Serialize(dobj, FAConfig);
282 } catch (const CircularReferenceError& ex) {
283 BOOST_THROW_EXCEPTION(ValidationError(dobj, ex.GetPath(), "Circular references are not allowed"));
286 Dictionary::Ptr persistentItem = new Dictionary({
287 { "type", type->GetName() },
288 { "name", GetName() },
289 { "properties", Serialize(dobj, FAConfig) },
290 { "debug_hints", dhint },
291 { "debug_info", new Array({
293 m_DebugInfo.FirstLine,
294 m_DebugInfo.FirstColumn,
295 m_DebugInfo.LastLine,
296 m_DebugInfo.LastColumn,
302 ConfigCompilerContext::GetInstance()->WriteObject(persistentItem);
303 persistentItem.reset();
313 * Registers the configuration item.
315 void ConfigItem::Register()
317 m_ActivationContext = ActivationContext::GetCurrentContext();
319 boost::mutex::scoped_lock lock(m_Mutex);
321 /* If this is a non-abstract object with a composite name
322 * we register it in m_UnnamedItems instead of m_Items. */
323 if (!m_Abstract && dynamic_cast<NameComposer *>(m_Type.get()))
324 m_UnnamedItems.emplace_back(this);
326 auto& items = m_Items[m_Type];
328 auto it = items.find(m_Name);
330 if (it != items.end()) {
331 std::ostringstream msgbuf;
332 msgbuf << "A configuration item of type '" << m_Type->GetName()
333 << "' and name '" << GetName() << "' already exists ("
334 << it->second->GetDebugInfo() << "), new declaration: " << GetDebugInfo();
335 BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str()));
338 m_Items[m_Type][m_Name] = this;
341 m_DefaultTemplates[m_Type][m_Name] = this;
346 * Unregisters the configuration item.
348 void ConfigItem::Unregister()
351 m_Object->Unregister();
355 boost::mutex::scoped_lock lock(m_Mutex);
356 m_UnnamedItems.erase(std::remove(m_UnnamedItems.begin(), m_UnnamedItems.end(), this), m_UnnamedItems.end());
357 m_Items[m_Type].erase(m_Name);
358 m_DefaultTemplates[m_Type].erase(m_Name);
362 * Retrieves a configuration item by type and name.
364 * @param type The type of the ConfigItem that is to be looked up.
365 * @param name The name of the ConfigItem that is to be looked up.
366 * @returns The configuration item.
368 ConfigItem::Ptr ConfigItem::GetByTypeAndName(const Type::Ptr& type, const String& name)
370 boost::mutex::scoped_lock lock(m_Mutex);
372 auto it = m_Items.find(type);
374 if (it == m_Items.end())
377 auto it2 = it->second.find(name);
379 if (it2 == it->second.end())
385 bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems)
387 typedef std::pair<ConfigItem::Ptr, bool> ItemPair;
388 std::vector<ItemPair> items;
391 boost::mutex::scoped_lock lock(m_Mutex);
393 for (const TypeMap::value_type& kv : m_Items) {
394 for (const ItemMap::value_type& kv2 : kv.second) {
395 if (kv2.second->m_Abstract || kv2.second->m_Object)
398 if (kv2.second->m_ActivationContext != context)
401 items.emplace_back(kv2.second, false);
405 ItemList newUnnamedItems;
407 for (const ConfigItem::Ptr& item : m_UnnamedItems) {
408 if (item->m_ActivationContext != context) {
409 newUnnamedItems.push_back(item);
413 if (item->m_Abstract || item->m_Object)
416 items.emplace_back(item, true);
419 m_UnnamedItems.swap(newUnnamedItems);
425 // Shuffle all items to evenly distribute them over the threads of the workqueue. This increases perfomance
426 // noticably in environments with lots of objects and available threads.
427 std::shuffle(std::begin(items), std::end(items), std::default_random_engine {});
430 Log(LogDebug, "configitem")
431 << "Committing " << items.size() << " new items.";
432 #endif /* I2_DEBUG */
434 for (const auto& ip : items)
435 newItems.push_back(ip.first);
437 std::set<Type::Ptr> types;
438 std::set<Type::Ptr> completed_types;
440 for (const Type::Ptr& type : Type::GetAllTypes()) {
441 if (ConfigObject::TypeInstance->IsAssignableFrom(type))
445 while (types.size() != completed_types.size()) {
446 for (const Type::Ptr& type : types) {
447 if (completed_types.find(type) != completed_types.end())
450 bool unresolved_dep = false;
452 /* skip this type (for now) if there are unresolved load dependencies */
453 for (const String& loadDep : type->GetLoadDependencies()) {
454 Type::Ptr pLoadDep = Type::GetByName(loadDep);
455 if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) {
456 unresolved_dep = true;
464 int committed_items = 0;
465 upq.ParallelFor(items, [&type, &committed_items](const ItemPair& ip) {
466 const ConfigItem::Ptr& item = ip.first;
468 if (item->m_Type != type)
471 ip.first->Commit(ip.second);
477 completed_types.insert(type);
480 if (committed_items > 0)
481 Log(LogDebug, "configitem")
482 << "Committed " << committed_items << " items of type '" << type->GetName() << "'.";
483 #endif /* I2_DEBUG */
485 if (upq.HasExceptions())
491 Log(LogDebug, "configitem")
492 << "Committed " << items.size() << " items.";
493 #endif /* I2_DEBUG */
495 completed_types.clear();
497 while (types.size() != completed_types.size()) {
498 for (const Type::Ptr& type : types) {
499 if (completed_types.find(type) != completed_types.end())
502 bool unresolved_dep = false;
504 /* skip this type (for now) if there are unresolved load dependencies */
505 for (const String& loadDep : type->GetLoadDependencies()) {
506 Type::Ptr pLoadDep = Type::GetByName(loadDep);
507 if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) {
508 unresolved_dep = true;
516 int notified_items = 0;
517 upq.ParallelFor(items, [&type, ¬ified_items](const ItemPair& ip) {
518 const ConfigItem::Ptr& item = ip.first;
520 if (!item->m_Object || item->m_Type != type)
524 item->m_Object->OnAllConfigLoaded();
526 } catch (const std::exception& ex) {
527 if (!item->m_IgnoreOnError)
530 Log(LogNotice, "ConfigObject")
531 << "Ignoring config object '" << item->m_Name << "' of type '" << item->m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex);
536 boost::mutex::scoped_lock lock(item->m_Mutex);
537 item->m_IgnoredItems.push_back(item->m_DebugInfo.Path);
542 completed_types.insert(type);
547 if (notified_items > 0)
548 Log(LogDebug, "configitem")
549 << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'.";
550 #endif /* I2_DEBUG */
552 if (upq.HasExceptions())
556 for (const String& loadDep : type->GetLoadDependencies()) {
557 upq.ParallelFor(items, [loadDep, &type, ¬ified_items](const ItemPair& ip) {
558 const ConfigItem::Ptr& item = ip.first;
560 if (!item->m_Object || item->m_Type->GetName() != loadDep)
563 ActivationScope ascope(item->m_ActivationContext);
564 item->m_Object->CreateChildObjects(type);
572 if (notified_items > 0)
573 Log(LogDebug, "configitem")
574 << "Sent CreateChildObjects to " << notified_items << " items of type '" << type->GetName() << "'.";
575 #endif /* I2_DEBUG */
577 if (upq.HasExceptions())
580 // Make sure to activate any additionally generated items
581 if (!CommitNewItems(context, upq, newItems))
589 bool ConfigItem::CommitItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems, bool silent)
592 Log(LogInformation, "ConfigItem", "Committing config item(s).");
594 if (!CommitNewItems(context, upq, newItems)) {
595 upq.ReportExceptions("config");
597 for (const ConfigItem::Ptr& item : newItems) {
604 ApplyRule::CheckMatches(silent);
607 /* log stats for external parsers */
608 typedef std::map<Type::Ptr, int> ItemCountMap;
609 ItemCountMap itemCounts;
610 for (const ConfigItem::Ptr& item : newItems) {
614 itemCounts[item->m_Object->GetReflectionType()]++;
617 for (const ItemCountMap::value_type& kv : itemCounts) {
618 Log(LogInformation, "ConfigItem")
619 << "Instantiated " << kv.second << " " << (kv.second != 1 ? kv.first->GetPluralName() : kv.first->GetName()) << ".";
626 bool ConfigItem::ActivateItems(WorkQueue& upq, const std::vector<ConfigItem::Ptr>& newItems, bool runtimeCreated, bool silent, bool withModAttrs)
628 static boost::mutex mtx;
629 boost::mutex::scoped_lock lock(mtx);
632 /* restore modified attributes */
633 if (Utility::PathExists(Configuration::ModAttrPath)) {
634 std::unique_ptr<Expression> expression = ConfigCompiler::CompileFile(Configuration::ModAttrPath);
638 ScriptFrame frame(true);
639 expression->Evaluate(frame);
640 } catch (const std::exception& ex) {
641 Log(LogCritical, "config", DiagnosticInformation(ex));
647 for (const ConfigItem::Ptr& item : newItems) {
651 ConfigObject::Ptr object = item->m_Object;
653 if (object->IsActive())
657 Log(LogDebug, "ConfigItem")
658 << "Setting 'active' to true for object '" << object->GetName() << "' of type '" << object->GetReflectionType()->GetName() << "'";
659 #endif /* I2_DEBUG */
661 object->PreActivate();
665 Log(LogInformation, "ConfigItem", "Triggering Start signal for config items");
667 /* Activate objects in priority order. */
668 std::vector<Type::Ptr> types = Type::GetAllTypes();
670 std::sort(types.begin(), types.end(), [](const Type::Ptr& a, const Type::Ptr& b) {
671 if (a->GetActivationPriority() < b->GetActivationPriority())
676 for (const Type::Ptr& type : types) {
677 for (const ConfigItem::Ptr& item : newItems) {
681 ConfigObject::Ptr object = item->m_Object;
682 Type::Ptr objectType = object->GetReflectionType();
684 if (objectType != type)
688 Log(LogDebug, "ConfigItem")
689 << "Activating object '" << object->GetName() << "' of type '"
690 << objectType->GetName() << "' with priority "
691 << objectType->GetActivationPriority();
692 #endif /* I2_DEBUG */
694 object->Activate(runtimeCreated);
700 if (upq.HasExceptions()) {
701 upq.ReportExceptions("ConfigItem");
706 for (const ConfigItem::Ptr& item : newItems) {
707 ConfigObject::Ptr object = item->m_Object;
712 ASSERT(object && object->IsActive());
714 #endif /* I2_DEBUG */
717 Log(LogInformation, "ConfigItem", "Activated all objects.");
722 bool ConfigItem::RunWithActivationContext(const Function::Ptr& function)
724 ActivationScope scope;
727 BOOST_THROW_EXCEPTION(ScriptError("'function' argument must not be null."));
731 WorkQueue upq(25000, Configuration::Concurrency);
732 upq.SetName("ConfigItem::RunWithActivationContext");
734 std::vector<ConfigItem::Ptr> newItems;
736 if (!CommitItems(scope.GetContext(), upq, newItems, true))
739 if (!ActivateItems(upq, newItems, false, true))
745 std::vector<ConfigItem::Ptr> ConfigItem::GetItems(const Type::Ptr& type)
747 std::vector<ConfigItem::Ptr> items;
749 boost::mutex::scoped_lock lock(m_Mutex);
751 auto it = m_Items.find(type);
753 if (it == m_Items.end())
756 items.reserve(it->second.size());
758 for (const ItemMap::value_type& kv : it->second) {
759 items.push_back(kv.second);
765 std::vector<ConfigItem::Ptr> ConfigItem::GetDefaultTemplates(const Type::Ptr& type)
767 std::vector<ConfigItem::Ptr> items;
769 boost::mutex::scoped_lock lock(m_Mutex);
771 auto it = m_DefaultTemplates.find(type);
773 if (it == m_DefaultTemplates.end())
776 items.reserve(it->second.size());
778 for (const ItemMap::value_type& kv : it->second) {
779 items.push_back(kv.second);
785 void ConfigItem::RemoveIgnoredItems(const String& allowedConfigPath)
787 boost::mutex::scoped_lock lock(m_Mutex);
789 for (const String& path : m_IgnoredItems) {
790 if (path.Find(allowedConfigPath) == String::NPos)
793 Log(LogNotice, "ConfigItem")
794 << "Removing ignored item path '" << path << "'.";
796 (void) unlink(path.CStr());
799 m_IgnoredItems.clear();