1 /******************************************************************************
3 * Copyright (C) 2012-2016 Icinga Development Team (https://www.icinga.org/) *
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. *
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. *
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 ******************************************************************************/
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"
40 #include <boost/foreach.hpp>
42 using namespace icinga;
44 boost::mutex ConfigItem::m_Mutex;
45 ConfigItem::TypeMap ConfigItem::m_Items;
46 ConfigItem::ItemList ConfigItem::m_UnnamedItems;
47 ConfigItem::IgnoredItemList ConfigItem::m_IgnoredItems;
49 REGISTER_SCRIPTFUNCTION_NS(Internal, run_with_activation_context, &ConfigItem::RunWithActivationContext);
52 * Constructor for the ConfigItem class.
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.
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),
74 * Retrieves the type of the configuration item.
78 String ConfigItem::GetType(void) const
84 * Retrieves the name of the configuration item.
88 String ConfigItem::GetName(void) const
94 * Checks whether the item is abstract.
96 * @returns true if the item is abstract, false otherwise.
98 bool ConfigItem::IsAbstract(void) const
104 * Retrieves the debug information for the configuration item.
106 * @returns The debug information.
108 DebugInfo ConfigItem::GetDebugInfo(void) const
113 Dictionary::Ptr ConfigItem::GetScope(void) const
118 ConfigObject::Ptr ConfigItem::GetObject(void) const
124 * Retrieves the expression list for the configuration item.
126 * @returns The expression list.
128 boost::shared_ptr<Expression> ConfigItem::GetExpression(void) const
134 * Retrieves the object filter for the configuration item.
136 * @returns The filter expression.
138 boost::shared_ptr<Expression> ConfigItem::GetFilter(void) const
143 class DefaultValidationUtils : public ValidationUtils
146 virtual bool ValidateName(const String& type, const String& name) const override
148 ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(type, name);
150 if (!item || (item && item->IsAbstract()))
158 * Commits the configuration item by creating a ConfigObject
161 * @returns The ConfigObject that was created/updated.
163 ConfigObject::Ptr ConfigItem::Commit(bool discard)
166 Log(LogDebug, "ConfigItem")
167 << "Commit called for ConfigItem Type=" << GetType() << ", Name=" << GetName();
168 #endif /* I2_DEBUG */
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));
176 return ConfigObject::Ptr();
178 ConfigObject::Ptr dobj = static_pointer_cast<ConfigObject>(type->Instantiate(std::vector<Value>()));
180 dobj->SetDebugInfo(m_DebugInfo);
181 dobj->SetZoneName(m_Zone);
182 dobj->SetPackage(m_Package);
183 dobj->SetName(m_Name);
185 DebugHint debugHints;
187 ScriptFrame frame(dobj);
189 m_Scope->CopyTo(frame.Locals);
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);
198 boost::mutex::scoped_lock lock(m_Mutex);
199 m_IgnoredItems.push_back(m_DebugInfo.Path);
202 return ConfigObject::Ptr();
209 m_Expression.reset();
212 String short_name = dobj->GetShortName();
214 if (!short_name.IsEmpty()) {
215 item_name = short_name;
216 dobj->SetName(short_name);
220 String name = item_name;
222 NameComposer *nc = dynamic_cast<NameComposer *>(type.get());
226 BOOST_THROW_EXCEPTION(ScriptError("Object name must not be empty.", m_DebugInfo));
228 name = nc->MakeName(name, dobj);
231 BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine name for object"));
234 if (name != item_name)
235 dobj->SetShortName(item_name);
239 Dictionary::Ptr dhint = debugHints.ToDictionary();
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);
250 boost::mutex::scoped_lock lock(m_Mutex);
251 m_IgnoredItems.push_back(m_DebugInfo.Path);
254 return ConfigObject::Ptr();
257 ex.SetDebugHint(dhint);
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);
269 boost::mutex::scoped_lock lock(m_Mutex);
270 m_IgnoredItems.push_back(m_DebugInfo.Path);
273 return ConfigObject::Ptr();
279 Dictionary::Ptr persistentItem = new Dictionary();
281 persistentItem->Set("type", GetType());
282 persistentItem->Set("name", GetName());
283 persistentItem->Set("properties", Serialize(dobj, FAConfig));
284 persistentItem->Set("debug_hints", dhint);
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);
294 ConfigCompilerContext::GetInstance()->WriteObject(persistentItem);
295 persistentItem.reset();
307 * Registers the configuration item.
309 void ConfigItem::Register(void)
311 Type::Ptr type = Type::GetByName(m_Type);
313 m_ActivationContext = ActivationContext::GetCurrentContext();
315 boost::mutex::scoped_lock lock(m_Mutex);
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);
322 ItemMap::const_iterator it = m_Items[m_Type].find(m_Name);
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()));
332 m_Items[m_Type][m_Name] = this;
337 * Unregisters the configuration item.
339 void ConfigItem::Unregister(void)
342 m_Object->Unregister();
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);
352 * Retrieves a configuration item by type and name.
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.
358 ConfigItem::Ptr ConfigItem::GetByTypeAndName(const String& type, const String& name)
360 boost::mutex::scoped_lock lock(m_Mutex);
362 ConfigItem::TypeMap::const_iterator it = m_Items.find(type);
364 if (it == m_Items.end())
365 return ConfigItem::Ptr();
367 ConfigItem::ItemMap::const_iterator it2 = it->second.find(name);
369 if (it2 == it->second.end())
370 return ConfigItem::Ptr();
375 void ConfigItem::OnAllConfigLoadedHelper(void)
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);
387 boost::mutex::scoped_lock lock(m_Mutex);
388 m_IgnoredItems.push_back(m_DebugInfo.Path);
398 void ConfigItem::CreateChildObjectsHelper(const Type::Ptr& type)
400 ActivationScope ascope(m_ActivationContext);
401 m_Object->CreateChildObjects(type);
404 bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems)
406 typedef std::pair<ConfigItem::Ptr, bool> ItemPair;
407 std::vector<ItemPair> items;
410 boost::mutex::scoped_lock lock(m_Mutex);
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)
417 if (kv2.second->m_ActivationContext != context)
420 items.push_back(std::make_pair(kv2.second, false));
424 ItemList newUnnamedItems;
426 BOOST_FOREACH(const ConfigItem::Ptr& item, m_UnnamedItems) {
427 if (item->m_ActivationContext != context) {
428 newUnnamedItems.push_back(item);
432 if (item->m_Abstract || item->m_Object)
435 items.push_back(std::make_pair(item, true));
438 m_UnnamedItems.swap(newUnnamedItems);
444 BOOST_FOREACH(const ItemPair& ip, items) {
445 newItems.push_back(ip.first);
446 upq.Enqueue(boost::bind(&ConfigItem::Commit, ip.first, ip.second));
451 if (upq.HasExceptions())
454 std::set<String> types;
456 BOOST_FOREACH(const Type::Ptr& type, Type::GetAllTypes()) {
457 if (ConfigObject::TypeInstance->IsAssignableFrom(type))
458 types.insert(type->GetName());
461 std::set<String> completed_types;
463 while (types.size() != completed_types.size()) {
464 BOOST_FOREACH(const String& type, types) {
465 if (completed_types.find(type) != completed_types.end())
468 Type::Ptr ptype = Type::GetByName(type);
469 bool unresolved_dep = false;
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;
482 BOOST_FOREACH(const ItemPair& ip, items) {
483 const ConfigItem::Ptr& item = ip.first;
488 if (item->m_Type == type)
489 upq.Enqueue(boost::bind(&ConfigItem::OnAllConfigLoadedHelper, item));
492 completed_types.insert(type);
496 if (upq.HasExceptions())
499 BOOST_FOREACH(const String& loadDep, ptype->GetLoadDependencies()) {
500 BOOST_FOREACH(const ItemPair& ip, items) {
501 const ConfigItem::Ptr& item = ip.first;
506 if (item->m_Type == loadDep)
507 upq.Enqueue(boost::bind(&ConfigItem::CreateChildObjectsHelper, item, ptype));
513 if (upq.HasExceptions())
516 if (!CommitNewItems(context, upq, newItems))
524 bool ConfigItem::CommitItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems, bool silent)
527 Log(LogInformation, "ConfigItem", "Committing config item(s).");
529 if (!CommitNewItems(context, upq, newItems)) {
530 upq.ReportExceptions("config");
532 BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
539 ApplyRule::CheckMatches();
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) {
549 itemCounts[item->m_Object->GetReflectionType()]++;
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()) << ".";
561 bool ConfigItem::ActivateItems(WorkQueue& upq, const std::vector<ConfigItem::Ptr>& newItems, bool runtimeCreated, bool silent)
563 static boost::mutex mtx;
564 boost::mutex::scoped_lock lock(mtx);
567 Log(LogInformation, "ConfigItem", "Triggering Start signal for config items");
569 BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
573 ConfigObject::Ptr object = item->m_Object;
575 if (object->IsActive())
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));
587 if (upq.HasExceptions()) {
588 upq.ReportExceptions("ConfigItem");
593 BOOST_FOREACH(const ConfigItem::Ptr& item, newItems) {
594 ConfigObject::Ptr object = item->m_Object;
599 ASSERT(object && object->IsActive());
601 #endif /* I2_DEBUG */
604 Log(LogInformation, "ConfigItem", "Activated all objects.");
609 bool ConfigItem::RunWithActivationContext(const Function::Ptr& function)
611 ActivationScope scope;
614 BOOST_THROW_EXCEPTION(ScriptError("'function' argument must not be null."));
618 WorkQueue upq(25000, Application::GetConcurrency());
619 upq.SetName("ConfigItem::RunWithActivationContext");
621 std::vector<ConfigItem::Ptr> newItems;
623 if (!CommitItems(scope.GetContext(), upq, newItems, true))
626 if (!ActivateItems(upq, newItems, false, true))
632 std::vector<ConfigItem::Ptr> ConfigItem::GetItems(const String& type)
634 std::vector<ConfigItem::Ptr> items;
636 boost::mutex::scoped_lock lock(m_Mutex);
638 TypeMap::const_iterator it = m_Items.find(type);
640 if (it == m_Items.end())
643 BOOST_FOREACH(const ItemMap::value_type& kv, it->second)
645 items.push_back(kv.second);
651 void ConfigItem::RemoveIgnoredItems(const String& allowedConfigPath)
653 boost::mutex::scoped_lock lock(m_Mutex);
655 BOOST_FOREACH(const String& path, m_IgnoredItems) {
656 if (path.Find(allowedConfigPath) == String::NPos)
659 Log(LogNotice, "ConfigItem")
660 << "Removing ignored item path '" << path << "'.";
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));
670 m_IgnoredItems.clear();