/****************************************************************************** * Icinga 2 * * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software Foundation * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ #include "i2-base.h" using namespace icinga; double DynamicObject::m_CurrentTx = 0; set DynamicObject::m_ModifiedObjects; boost::mutex DynamicObject::m_TransactionMutex; boost::once_flag DynamicObject::m_TransactionOnce; Timer::Ptr DynamicObject::m_TransactionTimer; signals2::signal DynamicObject::OnRegistered; signals2::signal DynamicObject::OnUnregistered; signals2::signal&)> DynamicObject::OnTransactionClosing; DynamicObject::DynamicObject(const Dictionary::Ptr& serializedObject) : m_ConfigTx(0) { RegisterAttribute("__name", Attribute_Config); RegisterAttribute("__type", Attribute_Config); RegisterAttribute("__local", Attribute_Config); RegisterAttribute("__abstract", Attribute_Config); RegisterAttribute("__source", Attribute_Local); RegisterAttribute("methods", Attribute_Config); if (!serializedObject->Contains("configTx")) BOOST_THROW_EXCEPTION(invalid_argument("Serialized object must contain a config snapshot.")); /* apply config state from the config item/remote update; * The DynamicObject::Create function takes care of restoring * non-config state after the object has been fully constructed */ ApplyUpdate(serializedObject, Attribute_Config); boost::call_once(m_TransactionOnce, &DynamicObject::Initialize); } /* * @threadsafety Always. */ DynamicObject::~DynamicObject(void) { boost::mutex::scoped_lock lock(m_TransactionMutex); m_ModifiedObjects.erase(this); } void DynamicObject::Initialize(void) { /* Set up a timer to periodically create a new transaction. */ m_TransactionTimer = boost::make_shared(); m_TransactionTimer->SetInterval(0.5); m_TransactionTimer->OnTimerExpired.connect(boost::bind(&DynamicObject::NewTx)); m_TransactionTimer->Start(); } /** * @threadsafety Always. */ void DynamicObject::SendLocalUpdateEvents(void) { map::iterator it; for (it = m_ModifiedAttributes.begin(); it != m_ModifiedAttributes.end(); it++) { OnAttributeChanged(it->first, it->second); } m_ModifiedAttributes.clear(); } Dictionary::Ptr DynamicObject::BuildUpdate(double sinceTx, int attributeTypes) const { DynamicObject::AttributeConstIterator it; Dictionary::Ptr attrs = boost::make_shared(); for (it = m_Attributes.begin(); it != m_Attributes.end(); it++) { if (it->second.Type == Attribute_Transient) continue; if ((it->second.Type & attributeTypes) == 0) continue; if (it->second.Tx == 0) continue; if (it->second.Tx < sinceTx && !(it->second.Type == Attribute_Config && m_ConfigTx >= sinceTx)) continue; Dictionary::Ptr attr = boost::make_shared(); attr->Set("data", it->second.Data); attr->Set("type", it->second.Type); attr->Set("tx", it->second.Tx); attrs->Set(it->first, attr); } Dictionary::Ptr update = boost::make_shared(); update->Set("attrs", attrs); if (m_ConfigTx >= sinceTx && attributeTypes & Attribute_Config) update->Set("configTx", m_ConfigTx); else if (attrs->GetLength() == 0) return Dictionary::Ptr(); return update; } void DynamicObject::ApplyUpdate(const Dictionary::Ptr& serializedUpdate, int allowedTypes) { double configTx = 0; if ((allowedTypes & Attribute_Config) != 0 && serializedUpdate->Contains("configTx")) { configTx = serializedUpdate->Get("configTx"); if (configTx > m_ConfigTx) ClearAttributesByType(Attribute_Config); } Dictionary::Ptr attrs = serializedUpdate->Get("attrs"); Dictionary::Iterator it; for (it = attrs->Begin(); it != attrs->End(); it++) { if (!it->second.IsObjectType()) continue; Dictionary::Ptr attr = it->second; int type = attr->Get("type"); if ((type & ~allowedTypes) != 0) continue; Value data = attr->Get("data"); double tx = attr->Get("tx"); if (type & Attribute_Config) RegisterAttribute(it->first, Attribute_Config); if (!HasAttribute(it->first)) RegisterAttribute(it->first, static_cast(type)); InternalSetAttribute(it->first, data, tx, true); } } void DynamicObject::RegisterAttribute(const String& name, DynamicAttributeType type) { DynamicAttribute attr; attr.Type = type; attr.Tx = 0; pair tt; tt = m_Attributes.insert(make_pair(name, attr)); if (!tt.second) tt.first->second.Type = type; } void DynamicObject::Set(const String& name, const Value& data) { InternalSetAttribute(name, data, GetCurrentTx()); } void DynamicObject::Touch(const String& name) { InternalSetAttribute(name, InternalGetAttribute(name), GetCurrentTx()); } Value DynamicObject::Get(const String& name) const { return InternalGetAttribute(name); } void DynamicObject::InternalSetAttribute(const String& name, const Value& data, double tx, bool allowEditConfig) { DynamicAttribute attr; attr.Type = Attribute_Transient; attr.Data = data; attr.Tx = tx; pair tt; tt = m_Attributes.insert(make_pair(name, attr)); Value oldValue; if (!allowEditConfig && (tt.first->second.Type & Attribute_Config)) BOOST_THROW_EXCEPTION(runtime_error("Config properties are immutable: '" + name + "'.")); if (!tt.second && tx >= tt.first->second.Tx) { oldValue = tt.first->second.Data; tt.first->second.Data = data; tt.first->second.Tx = tx; } if (tt.first->second.Type & Attribute_Config) m_ConfigTx = tx; { boost::mutex::scoped_lock lock(m_TransactionMutex); m_ModifiedObjects.insert(this); } /* Use insert() rather than [] so we don't overwrite * an existing oldValue if the attribute was previously * changed in the same transaction */ m_ModifiedAttributes.insert(make_pair(name, oldValue)); } Value DynamicObject::InternalGetAttribute(const String& name) const { DynamicObject::AttributeConstIterator it; it = m_Attributes.find(name); if (it == m_Attributes.end()) return Empty; return it->second.Data; } bool DynamicObject::HasAttribute(const String& name) const { return (m_Attributes.find(name) != m_Attributes.end()); } void DynamicObject::ClearAttributesByType(DynamicAttributeType type) { DynamicObject::AttributeIterator at; for (at = m_Attributes.begin(); at != m_Attributes.end(); at++) { if (at->second.Type != type) continue; at->second.Tx = 0; at->second.Data = Empty; } } DynamicType::Ptr DynamicObject::GetType(void) const { String name = Get("__type"); return DynamicType::GetByName(name); } String DynamicObject::GetName(void) const { return Get("__name"); } bool DynamicObject::IsLocal(void) const { Value value = Get("__local"); if (value.IsEmpty()) return false; return (value != 0); } bool DynamicObject::IsAbstract(void) const { Value value = Get("__abstract"); if (value.IsEmpty()) return false; return (value != 0); } void DynamicObject::SetSource(const String& value) { Set("__source", value); } String DynamicObject::GetSource(void) const { return Get("__source"); } void DynamicObject::Register(void) { DynamicType::Ptr dtype = GetType(); DynamicObject::Ptr dobj = dtype->GetObject(GetName()); DynamicObject::Ptr self = GetSelf(); assert(!dobj || dobj == self); dtype->RegisterObject(self); OnRegistered(GetSelf()); Start(); } void DynamicObject::Start(void) { /* Nothing to do here. */ } void DynamicObject::Unregister(void) { DynamicType::Ptr dtype = GetType(); ObjectLock olock(dtype); if (!dtype || !dtype->GetObject(GetName())) return; dtype->UnregisterObject(GetSelf()); OnUnregistered(GetSelf()); } ScriptTask::Ptr DynamicObject::InvokeMethod(const String& method, const vector& arguments, ScriptTask::CompletionCallback callback) { Value value = Get("methods"); if (!value.IsObjectType()) return ScriptTask::Ptr(); String funcName; Dictionary::Ptr methods = value; { ObjectLock olock(methods); if (!methods->Contains(method)) return ScriptTask::Ptr(); funcName = methods->Get(method); } ScriptFunction::Ptr func = ScriptFunction::GetByName(funcName); if (!func) BOOST_THROW_EXCEPTION(invalid_argument("Function '" + funcName + "' does not exist.")); ScriptTask::Ptr task = boost::make_shared(func, arguments); task->Start(callback); return task; } /* * @threadsafety Always. */ void DynamicObject::DumpObjects(const String& filename) { Logger::Write(LogInformation, "base", "Dumping program state to file '" + filename + "'"); String tempFilename = filename + ".tmp"; fstream fp; fp.open(tempFilename.CStr(), std::ios_base::out); if (!fp) BOOST_THROW_EXCEPTION(runtime_error("Could not open '" + filename + "' file")); StdioStream::Ptr sfp = boost::make_shared(&fp, false); sfp->Start(); DynamicType::Ptr type; BOOST_FOREACH(tie(tuples::ignore, type), DynamicType::GetTypes()) { DynamicObject::Ptr object; BOOST_FOREACH(tie(tuples::ignore, object), type->GetObjects()) { if (object->IsLocal()) continue; Dictionary::Ptr persistentObject = boost::make_shared(); persistentObject->Set("type", object->GetType()->GetName()); persistentObject->Set("name", object->GetName()); int types = Attribute_Local | Attribute_Replicated; /* only persist properties for replicated objects or for objects * that are marked as persistent */ if (!object->GetSource().IsEmpty() /*|| object->IsPersistent()*/) types |= Attribute_Config; Dictionary::Ptr update = object->BuildUpdate(0, types); if (!update) continue; persistentObject->Set("update", update); Value value = persistentObject; String json = value.Serialize(); NetString::WriteStringToStream(sfp, json); } } sfp->Close(); fp.close(); #ifdef _WIN32 _unlink(filename.CStr()); #endif /* _WIN32 */ if (rename(tempFilename.CStr(), filename.CStr()) < 0) BOOST_THROW_EXCEPTION(PosixException("rename() failed", errno)); } /* * @threadsafety Always. */ void DynamicObject::RestoreObjects(const String& filename) { Logger::Write(LogInformation, "base", "Restoring program state from file '" + filename + "'"); std::fstream fp; fp.open(filename.CStr(), std::ios_base::in); StdioStream::Ptr sfp = boost::make_shared(&fp, false); sfp->Start(); unsigned long restored = 0; String message; while (NetString::ReadStringFromStream(sfp, &message)) { Dictionary::Ptr persistentObject = Value::Deserialize(message); String type = persistentObject->Get("type"); String name = persistentObject->Get("name"); Dictionary::Ptr update = persistentObject->Get("update"); bool hasConfig = update->Contains("configTx"); DynamicType::Ptr dt = DynamicType::GetByName(type); ObjectLock dlock(dt); if (!dt) BOOST_THROW_EXCEPTION(invalid_argument("Invalid type: " + type)); DynamicObject::Ptr object = dt->GetObject(name); if (hasConfig && !object) { object = dt->CreateObject(update); object->Register(); } else if (object) { object->ApplyUpdate(update, Attribute_All); } restored++; } sfp->Close(); stringstream msgbuf; msgbuf << "Restored " << restored << " objects"; Logger::Write(LogDebug, "base", msgbuf.str()); } void DynamicObject::DeactivateObjects(void) { DynamicType::TypeMap::iterator tt; for (tt = DynamicType::GetTypes().begin(); tt != DynamicType::GetTypes().end(); tt++) { DynamicType::NameMap::iterator nt; while ((nt = tt->second->GetObjects().begin()) != tt->second->GetObjects().end()) { DynamicObject::Ptr object = nt->second; object->Unregister(); } } } /* * @threadsafety Always. */ double DynamicObject::GetCurrentTx(void) { boost::mutex::scoped_lock lock(m_TransactionMutex); assert(m_CurrentTx != 0); return m_CurrentTx; } /* * @threadsafety Always. Caller must not hold any Object locks. */ void DynamicObject::NewTx(void) { double tx; set objects; { boost::mutex::scoped_lock lock(m_TransactionMutex); tx = m_CurrentTx; m_ModifiedObjects.swap(objects); m_CurrentTx = Utility::GetTime(); } BOOST_FOREACH(DynamicObject *object, objects) { ObjectLock olock(object); object->SendLocalUpdateEvents(); } OnTransactionClosing(tx, objects); } void DynamicObject::OnInitCompleted(void) { } void DynamicObject::OnAttributeChanged(const String&, const Value&) { } /* * @threadsafety Always. */ DynamicObject::Ptr DynamicObject::GetObject(const String& type, const String& name) { DynamicType::Ptr dtype = DynamicType::GetByName(type); { ObjectLock olock(dtype); return dtype->GetObject(name); } } const DynamicObject::AttributeMap& DynamicObject::GetAttributes(void) const { return m_Attributes; }