/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/atomic-file.hpp" #include "base/configobject.hpp" #include "base/configobject-ti.cpp" #include "base/configtype.hpp" #include "base/serializer.hpp" #include "base/netstring.hpp" #include "base/json.hpp" #include "base/stdiostream.hpp" #include "base/debug.hpp" #include "base/objectlock.hpp" #include "base/logger.hpp" #include "base/exception.hpp" #include "base/function.hpp" #include "base/initialize.hpp" #include "base/workqueue.hpp" #include "base/context.hpp" #include "base/application.hpp" #include #include #include #include using namespace icinga; REGISTER_TYPE_WITH_PROTOTYPE(ConfigObject, ConfigObject::GetPrototype()); boost::signals2::signal ConfigObject::OnStateChanged; bool ConfigObject::IsActive() const { return GetActive(); } bool ConfigObject::IsPaused() const { return GetPaused(); } void ConfigObject::SetExtension(const String& key, const Value& value) { Dictionary::Ptr extensions = GetExtensions(); if (!extensions) { extensions = new Dictionary(); SetExtensions(extensions); } extensions->Set(key, value); } Value ConfigObject::GetExtension(const String& key) { Dictionary::Ptr extensions = GetExtensions(); if (!extensions) return Empty; return extensions->Get(key); } void ConfigObject::ClearExtension(const String& key) { Dictionary::Ptr extensions = GetExtensions(); if (!extensions) return; extensions->Remove(key); } class ModAttrValidationUtils final : public ValidationUtils { public: bool ValidateName(const String& type, const String& name) const override { Type::Ptr ptype = Type::GetByName(type); auto *dtype = dynamic_cast(ptype.get()); if (!dtype) return false; if (!dtype->GetObject(name)) return false; return true; } }; void ConfigObject::ModifyAttribute(const String& attr, const Value& value, bool updateVersion) { Dictionary::Ptr original_attributes = GetOriginalAttributes(); bool updated_original_attributes = false; Type::Ptr type = GetReflectionType(); std::vector tokens = attr.Split("."); String fieldName = tokens[0]; int fid = type->GetFieldId(fieldName); Field field = type->GetFieldInfo(fid); if (field.Attributes & FANoUserModify) BOOST_THROW_EXCEPTION(std::invalid_argument("Attribute cannot be modified.")); if (field.Attributes & FAConfig) { if (!original_attributes) { original_attributes = new Dictionary(); SetOriginalAttributes(original_attributes, true); } } Value oldValue = GetField(fid); Value newValue; if (tokens.size() > 1) { newValue = oldValue.Clone(); Value current = newValue; if (current.IsEmpty()) { current = new Dictionary(); newValue = current; } String prefix = tokens[0]; for (std::vector::size_type i = 1; i < tokens.size() - 1; i++) { if (!current.IsObjectType()) BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); Dictionary::Ptr dict = current; const String& key = tokens[i]; prefix += "." + key; if (!dict->Get(key, ¤t)) { current = new Dictionary(); dict->Set(key, current); } } if (!current.IsObjectType()) BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); Dictionary::Ptr dict = current; const String& key = tokens[tokens.size() - 1]; prefix += "." + key; /* clone it for original attributes */ oldValue = dict->Get(key).Clone(); if (field.Attributes & FAConfig) { updated_original_attributes = true; if (oldValue.IsObjectType()) { Dictionary::Ptr oldDict = oldValue; ObjectLock olock(oldDict); for (const auto& kv : oldDict) { String key = prefix + "." + kv.first; if (!original_attributes->Contains(key)) original_attributes->Set(key, kv.second); } /* store the new value as null */ if (value.IsObjectType()) { Dictionary::Ptr valueDict = value; ObjectLock olock(valueDict); for (const auto& kv : valueDict) { String key = attr + "." + kv.first; if (!original_attributes->Contains(key)) original_attributes->Set(key, Empty); } } } else if (!original_attributes->Contains(attr)) original_attributes->Set(attr, oldValue); } dict->Set(key, value); } else { newValue = value; if (field.Attributes & FAConfig) { if (!original_attributes->Contains(attr)) { updated_original_attributes = true; original_attributes->Set(attr, oldValue); } } } ModAttrValidationUtils utils; ValidateField(fid, Lazy{newValue}, utils); SetField(fid, newValue); if (updateVersion && (field.Attributes & FAConfig)) SetVersion(Utility::GetTime()); if (updated_original_attributes) NotifyOriginalAttributes(); } void ConfigObject::RestoreAttribute(const String& attr, bool updateVersion) { Type::Ptr type = GetReflectionType(); std::vector tokens = attr.Split("."); String fieldName = tokens[0]; int fid = type->GetFieldId(fieldName); Value currentValue = GetField(fid); Dictionary::Ptr original_attributes = GetOriginalAttributes(); if (!original_attributes) return; Value oldValue = original_attributes->Get(attr); Value newValue; if (tokens.size() > 1) { newValue = currentValue.Clone(); Value current = newValue; if (current.IsEmpty()) BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existent object attribute")); String prefix = tokens[0]; for (std::vector::size_type i = 1; i < tokens.size() - 1; i++) { if (!current.IsObjectType()) BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); Dictionary::Ptr dict = current; const String& key = tokens[i]; prefix += "." + key; if (!dict->Contains(key)) BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot restore non-existent object attribute")); current = dict->Get(key); } if (!current.IsObjectType()) BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); Dictionary::Ptr dict = current; const String& key = tokens[tokens.size() - 1]; prefix += "." + key; std::vector restoredAttrs; { ObjectLock olock(original_attributes); for (const auto& kv : original_attributes) { std::vector originalTokens = String(kv.first).Split("."); if (tokens.size() > originalTokens.size()) continue; bool match = true; for (std::vector::size_type i = 0; i < tokens.size(); i++) { if (tokens[i] != originalTokens[i]) { match = false; break; } } if (!match) continue; Dictionary::Ptr dict; if (tokens.size() == originalTokens.size()) dict = current; else { Value currentSub = current; for (std::vector::size_type i = tokens.size() - 1; i < originalTokens.size() - 1; i++) { dict = currentSub; currentSub = dict->Get(originalTokens[i]); if (!currentSub.IsObjectType()) { currentSub = new Dictionary(); dict->Set(originalTokens[i], currentSub); } } dict = currentSub; } dict->Set(originalTokens[originalTokens.size() - 1], kv.second); restoredAttrs.push_back(kv.first); } } for (const String& attr : restoredAttrs) original_attributes->Remove(attr); } else { newValue = oldValue; } original_attributes->Remove(attr); SetField(fid, newValue); if (updateVersion) SetVersion(Utility::GetTime()); } bool ConfigObject::IsAttributeModified(const String& attr) const { Dictionary::Ptr original_attributes = GetOriginalAttributes(); if (!original_attributes) return false; return original_attributes->Contains(attr); } void ConfigObject::Register() { ASSERT(!OwnsLock()); TypeImpl::Ptr type = static_pointer_cast >(GetReflectionType()); type->RegisterObject(this); } void ConfigObject::Unregister() { ASSERT(!OwnsLock()); TypeImpl::Ptr type = static_pointer_cast >(GetReflectionType()); type->UnregisterObject(this); } void ConfigObject::Start(bool runtimeCreated) { ObjectImpl::Start(runtimeCreated); ObjectLock olock(this); SetStartCalled(true); } void ConfigObject::PreActivate() { CONTEXT("Setting 'active' to true for object '" << GetName() << "' of type '" << GetReflectionType()->GetName() << "'"); ASSERT(!IsActive()); SetActive(true, true); } void ConfigObject::Activate(bool runtimeCreated, const Value& cookie) { CONTEXT("Activating object '" << GetName() << "' of type '" << GetReflectionType()->GetName() << "'"); { ObjectLock olock(this); Start(runtimeCreated); ASSERT(GetStartCalled()); if (GetHAMode() == HARunEverywhere) SetAuthority(true); } NotifyActive(cookie); } void ConfigObject::Stop(bool runtimeRemoved) { ObjectImpl::Stop(runtimeRemoved); ObjectLock olock(this); SetStopCalled(true); } void ConfigObject::Deactivate(bool runtimeRemoved, const Value& cookie) { CONTEXT("Deactivating object '" << GetName() << "' of type '" << GetReflectionType()->GetName() << "'"); { ObjectLock olock(this); if (!IsActive()) return; SetActive(false, true); SetAuthority(false); Stop(runtimeRemoved); } ASSERT(GetStopCalled()); NotifyActive(cookie); } void ConfigObject::OnConfigLoaded() { /* Nothing to do here. */ } void ConfigObject::OnAllConfigLoaded() { static ConfigType *ctype = dynamic_cast(Type::GetByName("Zone").get()); String zoneName = GetZoneName(); if (!zoneName.IsEmpty()) m_Zone = ctype->GetObject(zoneName); } void ConfigObject::CreateChildObjects(const Type::Ptr& childType) { /* Nothing to do here. */ } void ConfigObject::OnStateLoaded() { /* Nothing to do here. */ } void ConfigObject::Pause() { SetPauseCalled(true); } void ConfigObject::Resume() { SetResumeCalled(true); } void ConfigObject::SetAuthority(bool authority) { ObjectLock olock(this); if (authority && GetPaused()) { SetResumeCalled(false); Resume(); ASSERT(GetResumeCalled()); SetPaused(false); } else if (!authority && !GetPaused()) { SetPaused(true); SetPauseCalled(false); Pause(); ASSERT(GetPauseCalled()); } } void ConfigObject::DumpObjects(const String& filename, int attributeTypes) { Log(LogInformation, "ConfigObject") << "Dumping program state to file '" << filename << "'"; try { Utility::Glob(filename + ".tmp.*", &Utility::Remove, GlobFile); } catch (const std::exception& ex) { Log(LogWarning, "ConfigObject") << DiagnosticInformation(ex); } AtomicFile fp (filename, 0600); StdioStream::Ptr sfp = new StdioStream(&fp, false); for (const Type::Ptr& type : Type::GetAllTypes()) { auto *dtype = dynamic_cast(type.get()); if (!dtype) continue; for (const ConfigObject::Ptr& object : dtype->GetObjects()) { Dictionary::Ptr update = Serialize(object, attributeTypes); if (!update) continue; Dictionary::Ptr persistentObject = new Dictionary({ { "type", type->GetName() }, { "name", object->GetName() }, { "update", update } }); String json = JsonEncode(persistentObject); NetString::WriteStringToStream(sfp, json); } } sfp->Close(); fp.Commit(); } void ConfigObject::RestoreObject(const String& message, int attributeTypes) { Dictionary::Ptr persistentObject = JsonDecode(message); String type = persistentObject->Get("type"); String name = persistentObject->Get("name"); ConfigObject::Ptr object = GetObject(type, name); if (!object) return; #ifdef I2_DEBUG Log(LogDebug, "ConfigObject") << "Restoring object '" << name << "' of type '" << type << "'."; #endif /* I2_DEBUG */ Dictionary::Ptr update = persistentObject->Get("update"); Deserialize(object, update, false, attributeTypes); object->OnStateLoaded(); object->SetStateLoaded(true); } void ConfigObject::RestoreObjects(const String& filename, int attributeTypes) { if (!Utility::PathExists(filename)) return; Log(LogInformation, "ConfigObject") << "Restoring program state from file '" << filename << "'"; std::fstream fp; fp.open(filename.CStr(), std::ios_base::in); StdioStream::Ptr sfp = new StdioStream (&fp, false); unsigned long restored = 0; WorkQueue upq(25000, Configuration::Concurrency); upq.SetName("ConfigObject::RestoreObjects"); String message; StreamReadContext src; for (;;) { StreamReadStatus srs = NetString::ReadStringFromStream(sfp, &message, src); if (srs == StatusEof) break; if (srs != StatusNewItem) continue; upq.Enqueue([message, attributeTypes]() { RestoreObject(message, attributeTypes); }); restored++; } sfp->Close(); upq.Join(); unsigned long no_state = 0; for (const Type::Ptr& type : Type::GetAllTypes()) { auto *dtype = dynamic_cast(type.get()); if (!dtype) continue; for (const ConfigObject::Ptr& object : dtype->GetObjects()) { if (!object->GetStateLoaded()) { object->OnStateLoaded(); object->SetStateLoaded(true); no_state++; } } } Log(LogInformation, "ConfigObject") << "Restored " << restored << " objects. Loaded " << no_state << " new objects without state."; } void ConfigObject::StopObjects() { std::vector types = Type::GetAllTypes(); std::sort(types.begin(), types.end(), [](const Type::Ptr& a, const Type::Ptr& b) { if (a->GetActivationPriority() > b->GetActivationPriority()) return true; return false; }); for (const Type::Ptr& type : types) { auto *dtype = dynamic_cast(type.get()); if (!dtype) continue; for (const ConfigObject::Ptr& object : dtype->GetObjects()) { #ifdef I2_DEBUG Log(LogDebug, "ConfigObject") << "Deactivate() called for config object '" << object->GetName() << "' with type '" << type->GetName() << "'."; #endif /* I2_DEBUG */ object->Deactivate(); } } } void ConfigObject::DumpModifiedAttributes(const std::function& callback) { for (const Type::Ptr& type : Type::GetAllTypes()) { auto *dtype = dynamic_cast(type.get()); if (!dtype) continue; for (const ConfigObject::Ptr& object : dtype->GetObjects()) { Dictionary::Ptr originalAttributes = object->GetOriginalAttributes(); if (!originalAttributes) continue; ObjectLock olock(originalAttributes); for (const Dictionary::Pair& kv : originalAttributes) { String key = kv.first; Type::Ptr type = object->GetReflectionType(); std::vector tokens = key.Split("."); String fieldName = tokens[0]; int fid = type->GetFieldId(fieldName); Value currentValue = object->GetField(fid); Value modifiedValue; if (tokens.size() > 1) { Value current = currentValue; for (std::vector::size_type i = 1; i < tokens.size() - 1; i++) { if (!current.IsObjectType()) BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); Dictionary::Ptr dict = current; const String& key = tokens[i]; if (!dict->Contains(key)) break; current = dict->Get(key); } if (!current.IsObjectType()) BOOST_THROW_EXCEPTION(std::invalid_argument("Value must be a dictionary.")); Dictionary::Ptr dict = current; const String& key = tokens[tokens.size() - 1]; modifiedValue = dict->Get(key); } else modifiedValue = currentValue; callback(object, key, modifiedValue); } } } } ConfigObject::Ptr ConfigObject::GetObject(const String& type, const String& name) { Type::Ptr ptype = Type::GetByName(type); auto *ctype = dynamic_cast(ptype.get()); if (!ctype) return nullptr; return ctype->GetObject(name); } ConfigObject::Ptr ConfigObject::GetZone() const { return m_Zone; } Dictionary::Ptr ConfigObject::GetSourceLocation() const { DebugInfo di = GetDebugInfo(); return new Dictionary({ { "path", di.Path }, { "first_line", di.FirstLine }, { "first_column", di.FirstColumn }, { "last_line", di.LastLine }, { "last_column", di.LastColumn } }); } NameComposer::~NameComposer() { }