summaryrefslogtreecommitdiffstats
path: root/lib/base/configobject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/base/configobject.cpp')
-rw-r--r--lib/base/configobject.cpp701
1 files changed, 701 insertions, 0 deletions
diff --git a/lib/base/configobject.cpp b/lib/base/configobject.cpp
new file mode 100644
index 0000000..144afa4
--- /dev/null
+++ b/lib/base/configobject.cpp
@@ -0,0 +1,701 @@
+/* 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 <fstream>
+#include <boost/exception/errinfo_api_function.hpp>
+#include <boost/exception/errinfo_errno.hpp>
+#include <boost/exception/errinfo_file_name.hpp>
+
+using namespace icinga;
+
+REGISTER_TYPE_WITH_PROTOTYPE(ConfigObject, ConfigObject::GetPrototype());
+
+boost::signals2::signal<void (const ConfigObject::Ptr&)> 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<ConfigType *>(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<String> 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<String>::size_type i = 1; i < tokens.size() - 1; i++) {
+ if (!current.IsObjectType<Dictionary>())
+ 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, &current)) {
+ current = new Dictionary();
+ dict->Set(key, current);
+ }
+ }
+
+ if (!current.IsObjectType<Dictionary>())
+ 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>()) {
+ 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>()) {
+ 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<Value>{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<String> 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<String>::size_type i = 1; i < tokens.size() - 1; i++) {
+ if (!current.IsObjectType<Dictionary>())
+ 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<Dictionary>())
+ 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<String> restoredAttrs;
+
+ {
+ ObjectLock olock(original_attributes);
+ for (const auto& kv : original_attributes) {
+ std::vector<String> originalTokens = String(kv.first).Split(".");
+
+ if (tokens.size() > originalTokens.size())
+ continue;
+
+ bool match = true;
+ for (std::vector<String>::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<String>::size_type i = tokens.size() - 1; i < originalTokens.size() - 1; i++) {
+ dict = currentSub;
+ currentSub = dict->Get(originalTokens[i]);
+
+ if (!currentSub.IsObjectType<Dictionary>()) {
+ 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<ConfigObject>::Ptr type = static_pointer_cast<TypeImpl<ConfigObject> >(GetReflectionType());
+ type->RegisterObject(this);
+}
+
+void ConfigObject::Unregister()
+{
+ ASSERT(!OwnsLock());
+
+ TypeImpl<ConfigObject>::Ptr type = static_pointer_cast<TypeImpl<ConfigObject> >(GetReflectionType());
+ type->UnregisterObject(this);
+}
+
+void ConfigObject::Start(bool runtimeCreated)
+{
+ ObjectImpl<ConfigObject>::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<ConfigObject>::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<ConfigType *>(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<ConfigType *>(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<ConfigType *>(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<Type::Ptr> 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<ConfigType *>(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<void(const ConfigObject::Ptr&, const String&, const Value&)>& callback)
+{
+ for (const Type::Ptr& type : Type::GetAllTypes()) {
+ auto *dtype = dynamic_cast<ConfigType *>(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<String> 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<String>::size_type i = 1; i < tokens.size() - 1; i++) {
+ if (!current.IsObjectType<Dictionary>())
+ 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<Dictionary>())
+ 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<ConfigType *>(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()
+{ }