/****************************************************************************** * 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 = BOOST_ONCE_INIT; Timer::Ptr DynamicObject::m_TransactionTimer; signals2::signal DynamicObject::OnRegistered; signals2::signal DynamicObject::OnUnregistered; signals2::signal&)> DynamicObject::OnTransactionClosing; signals2::signal DynamicObject::OnFlushObject; DynamicObject::DynamicObject(const Dictionary::Ptr& serializedObject) : m_EventSafe(false), 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); { ObjectLock olock(serializedObject); 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 */ { ObjectLock olock(this); ApplyUpdate(serializedObject, Attribute_Config); } boost::call_once(m_TransactionOnce, &DynamicObject::Initialize); } /* * @threadsafety Always. */ DynamicObject::~DynamicObject(void) { } 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 attrs; { ObjectLock olock(this); attrs.swap(m_ModifiedAttributes); } /* Check if it's safe to send events. */ if (GetEventSafe()) { map::iterator it; for (it = attrs.begin(); it != attrs.end(); it++) OnAttributeChanged(it->first, it->second); } } 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); } attrs->Seal(); 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(); update->Seal(); return update; } void DynamicObject::ApplyUpdate(const Dictionary::Ptr& serializedUpdate, int allowedTypes) { Dictionary::Ptr attrs; { ObjectLock olock(serializedUpdate); double configTx = 0; if ((allowedTypes & Attribute_Config) != 0 && serializedUpdate->Contains("configTx")) { configTx = serializedUpdate->Get("configTx"); if (configTx > m_ConfigTx) ClearAttributesByType(Attribute_Config); } attrs = serializedUpdate->Get("attrs"); } { ObjectLock olock(attrs); Dictionary::Iterator it; for (it = attrs->Begin(); it != attrs->End(); it++) { if (!it->second.IsObjectType()) continue; Dictionary::Ptr attr = it->second; ObjectLock alock(attr); 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; if (GetEventSafe()) { /* We can't call GetSelf() in the constructor or destructor. * The OnConstructionCompleted() function will take care of adding this * object to the list of modified objects later on if we can't * do it here. */ DynamicObject::Ptr self = GetSelf(); { boost::mutex::scoped_lock lock(m_TransactionMutex); m_ModifiedObjects.insert(self); } } /* 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(); ObjectLock olock(dtype); DynamicObject::Ptr dobj = dtype->GetObject(GetName()); DynamicObject::Ptr self = GetSelf(); assert(!dobj || dobj == self); if (!dobj) 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::MakeMethodTask(const String& method, const vector& arguments) { 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.")); return boost::make_shared(func, arguments); } /* * @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(); ; BOOST_FOREACH(const DynamicType::Ptr& type, DynamicType::GetTypes()) { BOOST_FOREACH(const DynamicObject::Ptr& 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(LogInformation, "base", msgbuf.str()); } void DynamicObject::DeactivateObjects(void) { BOOST_FOREACH(const DynamicType::Ptr& dt, DynamicType::GetTypes()) { set objects; { ObjectLock olock(dt); objects = dt->GetObjects(); } BOOST_FOREACH(const DynamicObject::Ptr& object, objects) { ObjectLock olock(object); object->Unregister(); } } } /* * @threadsafety Always. */ double DynamicObject::GetCurrentTx(void) { boost::mutex::scoped_lock lock(m_TransactionMutex); if (m_CurrentTx == 0) { /* Set the initial transaction ID. */ m_CurrentTx = Utility::GetTime(); } return m_CurrentTx; } void DynamicObject::Flush(void) { SendLocalUpdateEvents(); OnFlushObject(GetSelf()); } /* * @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(const DynamicObject::WeakPtr& wobject, objects) { DynamicObject::Ptr object = wobject.lock(); if (!object) continue; object->SendLocalUpdateEvents(); } OnTransactionClosing(tx, objects); } void DynamicObject::OnConstructionCompleted(void) { /* It's now safe to send us attribute events. */ SetEventSafe(true); /* Add this new object to the list of modified objects. * We're doing this here because we can't construct * a while WeakPtr from within the object's constructor. */ boost::mutex::scoped_lock lock(m_TransactionMutex); m_ModifiedObjects.insert(GetSelf()); } void DynamicObject::OnRegistrationCompleted(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; } void DynamicObject::SetEventSafe(bool safe) { m_EventSafe = safe; } bool DynamicObject::GetEventSafe(void) const { return m_EventSafe; }