diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:32:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:32:39 +0000 |
commit | 56ae875861ab260b80a030f50c4aff9f9dc8fff0 (patch) | |
tree | 531412110fc901a5918c7f7442202804a83cada9 /lib/db_ido | |
parent | Initial commit. (diff) | |
download | icinga2-56ae875861ab260b80a030f50c4aff9f9dc8fff0.tar.xz icinga2-56ae875861ab260b80a030f50c4aff9f9dc8fff0.zip |
Adding upstream version 2.14.2.upstream/2.14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/db_ido')
40 files changed, 5805 insertions, 0 deletions
diff --git a/lib/db_ido/CMakeLists.txt b/lib/db_ido/CMakeLists.txt new file mode 100644 index 0000000..7a97d27 --- /dev/null +++ b/lib/db_ido/CMakeLists.txt @@ -0,0 +1,40 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +mkclass_target(dbconnection.ti dbconnection-ti.cpp dbconnection-ti.hpp) + +mkembedconfig_target(db_ido-itl.conf db_ido-itl.cpp) + +set(db_ido_SOURCES + i2-db_ido.hpp db_ido-itl.cpp + commanddbobject.cpp commanddbobject.hpp + dbconnection.cpp dbconnection.hpp dbconnection-ti.hpp + dbevents.cpp dbevents.hpp + dbobject.cpp dbobject.hpp + dbquery.cpp dbquery.hpp + dbreference.cpp dbreference.hpp + dbtype.cpp dbtype.hpp + dbvalue.cpp dbvalue.hpp + endpointdbobject.cpp endpointdbobject.hpp + hostdbobject.cpp hostdbobject.hpp + hostgroupdbobject.cpp hostgroupdbobject.hpp + idochecktask.cpp idochecktask.hpp + servicedbobject.cpp servicedbobject.hpp + servicegroupdbobject.cpp servicegroupdbobject.hpp + timeperioddbobject.cpp timeperioddbobject.hpp + userdbobject.cpp userdbobject.hpp + usergroupdbobject.cpp usergroupdbobject.hpp + zonedbobject.cpp zonedbobject.hpp +) + +if(ICINGA2_UNITY_BUILD) + mkunity_target(db_ido db_ido db_ido_SOURCES) +endif() + +add_library(db_ido OBJECT ${db_ido_SOURCES}) + +add_dependencies(db_ido base config icinga remote) + +set_target_properties ( + db_ido PROPERTIES + FOLDER Lib +) diff --git a/lib/db_ido/commanddbobject.cpp b/lib/db_ido/commanddbobject.cpp new file mode 100644 index 0000000..2ac167a --- /dev/null +++ b/lib/db_ido/commanddbobject.cpp @@ -0,0 +1,31 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/commanddbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/command.hpp" +#include "icinga/compatutility.hpp" +#include "base/objectlock.hpp" +#include "base/convert.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(Command, "command", DbObjectTypeCommand, "object_id", CommandDbObject); + +CommandDbObject::CommandDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr CommandDbObject::GetConfigFields() const +{ + Command::Ptr command = static_pointer_cast<Command>(GetObject()); + + return new Dictionary({ + { "command_line", CompatUtility::GetCommandLine(command) } + }); +} + +Dictionary::Ptr CommandDbObject::GetStatusFields() const +{ + return nullptr; +} diff --git a/lib/db_ido/commanddbobject.hpp b/lib/db_ido/commanddbobject.hpp new file mode 100644 index 0000000..6d22747 --- /dev/null +++ b/lib/db_ido/commanddbobject.hpp @@ -0,0 +1,30 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef COMMANDDBOBJECT_H +#define COMMANDDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A Command database object. + * + * @ingroup ido + */ +class CommandDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(CommandDbObject); + + CommandDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; +}; + +} + +#endif /* COMMANDDBOBJECT_H */ diff --git a/lib/db_ido/db_ido-itl.conf b/lib/db_ido/db_ido-itl.conf new file mode 100644 index 0000000..e2c42c3 --- /dev/null +++ b/lib/db_ido/db_ido-itl.conf @@ -0,0 +1,19 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +System.assert(Internal.run_with_activation_context(function() { + template CheckCommand "ido-check-command" use (checkFunc = Internal.IdoCheck) { + execute = checkFunc + } + + object CheckCommand "ido" { + import "ido-check-command" + } +})) + +var methods = [ + "IdoCheck" +] + +for (method in methods) { + Internal.remove(method) +} diff --git a/lib/db_ido/dbconnection.cpp b/lib/db_ido/dbconnection.cpp new file mode 100644 index 0000000..a8534c4 --- /dev/null +++ b/lib/db_ido/dbconnection.cpp @@ -0,0 +1,583 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbconnection.hpp" +#include "db_ido/dbconnection-ti.cpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/icingaapplication.hpp" +#include "icinga/host.hpp" +#include "icinga/service.hpp" +#include "base/configtype.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/utility.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +REGISTER_TYPE(DbConnection); + +Timer::Ptr DbConnection::m_ProgramStatusTimer; +boost::once_flag DbConnection::m_OnceFlag = BOOST_ONCE_INIT; + +void DbConnection::OnConfigLoaded() +{ + ConfigObject::OnConfigLoaded(); + + Value categories = GetCategories(); + + SetCategoryFilter(FilterArrayToInt(categories, DbQuery::GetCategoryFilterMap(), DbCatEverything)); + + if (!GetEnableHa()) { + Log(LogDebug, "DbConnection") + << "HA functionality disabled. Won't pause IDO connection: " << GetName(); + + SetHAMode(HARunEverywhere); + } + + boost::call_once(m_OnceFlag, InitializeDbTimer); +} + +void DbConnection::Start(bool runtimeCreated) +{ + ObjectImpl<DbConnection>::Start(runtimeCreated); + + Log(LogInformation, "DbConnection") + << "'" << GetName() << "' started."; + + auto onQuery = [this](const DbQuery& query) { ExecuteQuery(query); }; + DbObject::OnQuery.connect(onQuery); + + auto onMultipleQueries = [this](const std::vector<DbQuery>& multiQueries) { ExecuteMultipleQueries(multiQueries); }; + DbObject::OnMultipleQueries.connect(onMultipleQueries); + + DbObject::QueryCallbacks queryCallbacks; + queryCallbacks.Query = onQuery; + queryCallbacks.MultipleQueries = onMultipleQueries; + + DbObject::OnMakeQueries.connect([queryCallbacks](const std::function<void (const DbObject::QueryCallbacks&)>& queryFunc) { + queryFunc(queryCallbacks); + }); +} + +void DbConnection::Stop(bool runtimeRemoved) +{ + Log(LogInformation, "DbConnection") + << "'" << GetName() << "' stopped."; + + ObjectImpl<DbConnection>::Stop(runtimeRemoved); +} + +void DbConnection::EnableActiveChangedHandler() +{ + if (!m_ActiveChangedHandler) { + ConfigObject::OnActiveChanged.connect([this](const ConfigObject::Ptr& object, const Value&) { UpdateObject(object); }); + m_ActiveChangedHandler = true; + } +} + +void DbConnection::Resume() +{ + ConfigObject::Resume(); + + Log(LogInformation, "DbConnection") + << "Resuming IDO connection: " << GetName(); + + m_CleanUpTimer = Timer::Create(); + m_CleanUpTimer->SetInterval(60); + m_CleanUpTimer->OnTimerExpired.connect([this](const Timer * const&) { CleanUpHandler(); }); + m_CleanUpTimer->Start(); + + m_LogStatsTimeout = 0; + + m_LogStatsTimer = Timer::Create(); + m_LogStatsTimer->SetInterval(10); + m_LogStatsTimer->OnTimerExpired.connect([this](const Timer * const&) { LogStatsHandler(); }); + m_LogStatsTimer->Start(); +} + +void DbConnection::Pause() +{ + Log(LogInformation, "DbConnection") + << "Pausing IDO connection: " << GetName(); + + m_LogStatsTimer->Stop(true); + m_CleanUpTimer->Stop(true); + + DbQuery query1; + query1.Table = "programstatus"; + query1.IdColumn = "programstatus_id"; + query1.Type = DbQueryUpdate; + query1.Category = DbCatProgramStatus; + query1.WhereCriteria = new Dictionary({ + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + query1.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "program_end_time", DbValue::FromTimestamp(Utility::GetTime()) }, + { "is_currently_running", 0 }, + { "process_id", Empty } + }); + + query1.Priority = PriorityHigh; + + ExecuteQuery(query1); + + NewTransaction(); + + m_QueryQueue.Enqueue([this]() { Disconnect(); }, PriorityLow); + + /* Work on remaining tasks but never delete the threads, for HA resuming later. */ + m_QueryQueue.Join(); + + ConfigObject::Pause(); +} + +void DbConnection::InitializeDbTimer() +{ + m_ProgramStatusTimer = Timer::Create(); + m_ProgramStatusTimer->SetInterval(10); + m_ProgramStatusTimer->OnTimerExpired.connect([](const Timer * const&) { UpdateProgramStatus(); }); + m_ProgramStatusTimer->Start(); +} + +void DbConnection::InsertRuntimeVariable(const String& key, const Value& value) +{ + DbQuery query; + query.Table = "runtimevariables"; + query.Type = DbQueryInsert; + query.Category = DbCatProgramStatus; + query.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "varname", key }, + { "varvalue", value } + }); + DbObject::OnQuery(query); +} + +void DbConnection::UpdateProgramStatus() +{ + IcingaApplication::Ptr icingaApplication = IcingaApplication::GetInstance(); + + if (!icingaApplication) + return; + + Log(LogNotice, "DbConnection") + << "Updating programstatus table."; + + std::vector<DbQuery> queries; + + DbQuery query1; + query1.Type = DbQueryNewTransaction; + query1.Priority = PriorityImmediate; + queries.emplace_back(std::move(query1)); + + DbQuery query2; + query2.Table = "programstatus"; + query2.IdColumn = "programstatus_id"; + query2.Type = DbQueryInsert | DbQueryDelete; + query2.Category = DbCatProgramStatus; + + query2.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "program_version", Application::GetAppVersion() }, + { "status_update_time", DbValue::FromTimestamp(Utility::GetTime()) }, + { "program_start_time", DbValue::FromTimestamp(Application::GetStartTime()) }, + { "is_currently_running", 1 }, + { "endpoint_name", icingaApplication->GetNodeName() }, + { "process_id", Utility::GetPid() }, + { "daemon_mode", 1 }, + { "last_command_check", DbValue::FromTimestamp(Utility::GetTime()) }, + { "notifications_enabled", (icingaApplication->GetEnableNotifications() ? 1 : 0) }, + { "active_host_checks_enabled", (icingaApplication->GetEnableHostChecks() ? 1 : 0) }, + { "passive_host_checks_enabled", 1 }, + { "active_service_checks_enabled", (icingaApplication->GetEnableServiceChecks() ? 1 : 0) }, + { "passive_service_checks_enabled", 1 }, + { "event_handlers_enabled", (icingaApplication->GetEnableEventHandlers() ? 1 : 0) }, + { "flap_detection_enabled", (icingaApplication->GetEnableFlapping() ? 1 : 0) }, + { "process_performance_data", (icingaApplication->GetEnablePerfdata() ? 1 : 0) } + }); + + query2.WhereCriteria = new Dictionary({ + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + queries.emplace_back(std::move(query2)); + + DbQuery query3; + query3.Type = DbQueryNewTransaction; + queries.emplace_back(std::move(query3)); + + DbObject::OnMultipleQueries(queries); + + DbQuery query4; + query4.Table = "runtimevariables"; + query4.Type = DbQueryDelete; + query4.Category = DbCatProgramStatus; + query4.WhereCriteria = new Dictionary({ + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + DbObject::OnQuery(query4); + + InsertRuntimeVariable("total_services", ConfigType::Get<Service>()->GetObjectCount()); + InsertRuntimeVariable("total_scheduled_services", ConfigType::Get<Service>()->GetObjectCount()); + InsertRuntimeVariable("total_hosts", ConfigType::Get<Host>()->GetObjectCount()); + InsertRuntimeVariable("total_scheduled_hosts", ConfigType::Get<Host>()->GetObjectCount()); +} + +void DbConnection::CleanUpHandler() +{ + auto now = static_cast<long>(Utility::GetTime()); + + struct { + String name; + String time_column; + } tables[] = { + { "acknowledgements", "entry_time" }, + { "commenthistory", "entry_time" }, + { "contactnotifications", "start_time" }, + { "contactnotificationmethods", "start_time" }, + { "downtimehistory", "entry_time" }, + { "eventhandlers", "start_time" }, + { "externalcommands", "entry_time" }, + { "flappinghistory", "event_time" }, + { "hostchecks", "start_time" }, + { "logentries", "logentry_time" }, + { "notifications", "start_time" }, + { "processevents", "event_time" }, + { "statehistory", "state_time" }, + { "servicechecks", "start_time" }, + { "systemcommands", "start_time" } + }; + + for (auto& table : tables) { + double max_age = GetCleanup()->Get(table.name + "_age"); + + if (max_age == 0) + continue; + + CleanUpExecuteQuery(table.name, table.time_column, now - max_age); + Log(LogNotice, "DbConnection") + << "Cleanup (" << table.name << "): " << max_age + << " now: " << now + << " old: " << now - max_age; + } + +} + +void DbConnection::LogStatsHandler() +{ + if (!GetConnected() || IsPaused()) + return; + + auto pending = m_PendingQueries.load(); + + auto now = Utility::GetTime(); + bool timeoutReached = m_LogStatsTimeout < now; + + if (pending == 0u && !timeoutReached) { + return; + } + + auto output = round(m_OutputQueries.CalculateRate(now, 10)); + + if (pending < output * 5 && !timeoutReached) { + return; + } + + auto input = round(m_InputQueries.CalculateRate(now, 10)); + + Log(LogInformation, GetReflectionType()->GetName()) + << "Pending queries: " << pending << " (Input: " << input + << "/s; Output: " << output << "/s)"; + + /* Reschedule next log entry in 5 minutes. */ + if (timeoutReached) { + m_LogStatsTimeout = now + 60 * 5; + } +} + +void DbConnection::CleanUpExecuteQuery(const String&, const String&, double) +{ + /* Default handler does nothing. */ +} + +void DbConnection::SetConfigHash(const DbObject::Ptr& dbobj, const String& hash) +{ + SetConfigHash(dbobj->GetType(), GetObjectID(dbobj), hash); +} + +void DbConnection::SetConfigHash(const DbType::Ptr& type, const DbReference& objid, const String& hash) +{ + if (!objid.IsValid()) + return; + + if (!hash.IsEmpty()) + m_ConfigHashes[std::make_pair(type, objid)] = hash; + else + m_ConfigHashes.erase(std::make_pair(type, objid)); +} + +String DbConnection::GetConfigHash(const DbObject::Ptr& dbobj) const +{ + return GetConfigHash(dbobj->GetType(), GetObjectID(dbobj)); +} + +String DbConnection::GetConfigHash(const DbType::Ptr& type, const DbReference& objid) const +{ + if (!objid.IsValid()) + return String(); + + auto it = m_ConfigHashes.find(std::make_pair(type, objid)); + + if (it == m_ConfigHashes.end()) + return String(); + + return it->second; +} + +void DbConnection::SetObjectID(const DbObject::Ptr& dbobj, const DbReference& dbref) +{ + if (dbref.IsValid()) + m_ObjectIDs[dbobj] = dbref; + else + m_ObjectIDs.erase(dbobj); +} + +DbReference DbConnection::GetObjectID(const DbObject::Ptr& dbobj) const +{ + auto it = m_ObjectIDs.find(dbobj); + + if (it == m_ObjectIDs.end()) + return {}; + + return it->second; +} + +void DbConnection::SetInsertID(const DbObject::Ptr& dbobj, const DbReference& dbref) +{ + SetInsertID(dbobj->GetType(), GetObjectID(dbobj), dbref); +} + +void DbConnection::SetInsertID(const DbType::Ptr& type, const DbReference& objid, const DbReference& dbref) +{ + if (!objid.IsValid()) + return; + + if (dbref.IsValid()) + m_InsertIDs[std::make_pair(type, objid)] = dbref; + else + m_InsertIDs.erase(std::make_pair(type, objid)); +} + +DbReference DbConnection::GetInsertID(const DbObject::Ptr& dbobj) const +{ + return GetInsertID(dbobj->GetType(), GetObjectID(dbobj)); +} + +DbReference DbConnection::GetInsertID(const DbType::Ptr& type, const DbReference& objid) const +{ + if (!objid.IsValid()) + return {}; + + auto it = m_InsertIDs.find(std::make_pair(type, objid)); + + if (it == m_InsertIDs.end()) + return DbReference(); + + return it->second; +} + +void DbConnection::SetObjectActive(const DbObject::Ptr& dbobj, bool active) +{ + if (active) + m_ActiveObjects.insert(dbobj); + else + m_ActiveObjects.erase(dbobj); +} + +bool DbConnection::GetObjectActive(const DbObject::Ptr& dbobj) const +{ + return (m_ActiveObjects.find(dbobj) != m_ActiveObjects.end()); +} + +void DbConnection::ClearIDCache() +{ + SetIDCacheValid(false); + + m_ObjectIDs.clear(); + m_InsertIDs.clear(); + m_ActiveObjects.clear(); + m_ConfigUpdates.clear(); + m_StatusUpdates.clear(); + m_ConfigHashes.clear(); +} + +void DbConnection::SetConfigUpdate(const DbObject::Ptr& dbobj, bool hasupdate) +{ + if (hasupdate) + m_ConfigUpdates.insert(dbobj); + else + m_ConfigUpdates.erase(dbobj); +} + +bool DbConnection::GetConfigUpdate(const DbObject::Ptr& dbobj) const +{ + return (m_ConfigUpdates.find(dbobj) != m_ConfigUpdates.end()); +} + +void DbConnection::SetStatusUpdate(const DbObject::Ptr& dbobj, bool hasupdate) +{ + if (hasupdate) + m_StatusUpdates.insert(dbobj); + else + m_StatusUpdates.erase(dbobj); +} + +bool DbConnection::GetStatusUpdate(const DbObject::Ptr& dbobj) const +{ + return (m_StatusUpdates.find(dbobj) != m_StatusUpdates.end()); +} + +void DbConnection::UpdateObject(const ConfigObject::Ptr& object) +{ + bool isShuttingDown = Application::IsShuttingDown(); + bool isRestarting = Application::IsRestarting(); + +#ifdef I2_DEBUG + if (isShuttingDown || isRestarting) { + //Log(LogDebug, "DbConnection") + // << "Updating object '" << object->GetName() << "' \t\t active '" << Convert::ToLong(object->IsActive()) + // << "' shutting down '" << Convert::ToLong(isShuttingDown) << "' restarting '" << Convert::ToLong(isRestarting) << "'."; + } +#endif /* I2_DEBUG */ + + /* Wait until a database connection is established on reconnect. */ + if (!GetConnected()) + return; + + /* Don't update inactive objects during shutdown/reload/restart. + * They would be marked as deleted. This gets triggered with ConfigObject::StopObjects(). + * During startup/reconnect this is fine, the handler is not active there. + */ + if (isShuttingDown || isRestarting) + return; + + DbObject::Ptr dbobj = DbObject::GetOrCreateByObject(object); + + if (dbobj) { + bool dbActive = GetObjectActive(dbobj); + bool active = object->IsActive(); + + if (active) { + if (!dbActive) + ActivateObject(dbobj); + + Dictionary::Ptr configFields = dbobj->GetConfigFields(); + String configHash = dbobj->CalculateConfigHash(configFields); + ASSERT(configHash.GetLength() <= 64); + configFields->Set("config_hash", configHash); + + String cachedHash = GetConfigHash(dbobj); + + if (cachedHash != configHash) { + dbobj->SendConfigUpdateHeavy(configFields); + dbobj->SendStatusUpdate(); + } else { + dbobj->SendConfigUpdateLight(); + } + } else if (!active) { + /* This may happen on reload/restart actions too + * and is blocked above already. + * + * Deactivate the deleted object no matter + * which state it had in the database. + */ + DeactivateObject(dbobj); + } + } +} + +void DbConnection::UpdateAllObjects() +{ + for (const Type::Ptr& type : Type::GetAllTypes()) { + auto *dtype = dynamic_cast<ConfigType *>(type.get()); + + if (!dtype) + continue; + + for (const ConfigObject::Ptr& object : dtype->GetObjects()) { + m_QueryQueue.Enqueue([this, object](){ UpdateObject(object); }, PriorityHigh); + } + } +} + +void DbConnection::PrepareDatabase() +{ + for (const DbType::Ptr& type : DbType::GetAllTypes()) { + FillIDCache(type); + } +} + +void DbConnection::ValidateFailoverTimeout(const Lazy<double>& lvalue, const ValidationUtils& utils) +{ + ObjectImpl<DbConnection>::ValidateFailoverTimeout(lvalue, utils); + + if (lvalue() < 30) + BOOST_THROW_EXCEPTION(ValidationError(this, { "failover_timeout" }, "Failover timeout minimum is 30s.")); +} + +void DbConnection::ValidateCategories(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) +{ + ObjectImpl<DbConnection>::ValidateCategories(lvalue, utils); + + int filter = FilterArrayToInt(lvalue(), DbQuery::GetCategoryFilterMap(), 0); + + if (filter != DbCatEverything && (filter & ~(DbCatInvalid | DbCatEverything | DbCatConfig | DbCatState | + DbCatAcknowledgement | DbCatComment | DbCatDowntime | DbCatEventHandler | DbCatExternalCommand | + DbCatFlapping | DbCatLog | DbCatNotification | DbCatProgramStatus | DbCatRetention | + DbCatStateHistory)) != 0) + BOOST_THROW_EXCEPTION(ValidationError(this, { "categories" }, "categories filter is invalid.")); +} + +void DbConnection::IncreaseQueryCount() +{ + double now = Utility::GetTime(); + + std::unique_lock<std::mutex> lock(m_StatsMutex); + m_QueryStats.InsertValue(now, 1); +} + +int DbConnection::GetQueryCount(RingBuffer::SizeType span) +{ + std::unique_lock<std::mutex> lock(m_StatsMutex); + return m_QueryStats.UpdateAndGetValues(Utility::GetTime(), span); +} + +bool DbConnection::IsIDCacheValid() const +{ + return m_IDCacheValid; +} + +void DbConnection::SetIDCacheValid(bool valid) +{ + m_IDCacheValid = valid; +} + +int DbConnection::GetSessionToken() +{ + return Application::GetStartTime(); +} + +void DbConnection::IncreasePendingQueries(int count) +{ + m_PendingQueries.fetch_add(count); + m_InputQueries.InsertValue(Utility::GetTime(), count); +} + +void DbConnection::DecreasePendingQueries(int count) +{ + m_PendingQueries.fetch_sub(count); + m_OutputQueries.InsertValue(Utility::GetTime(), count); +} diff --git a/lib/db_ido/dbconnection.hpp b/lib/db_ido/dbconnection.hpp new file mode 100644 index 0000000..517a8a4 --- /dev/null +++ b/lib/db_ido/dbconnection.hpp @@ -0,0 +1,138 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBCONNECTION_H +#define DBCONNECTION_H + +#include "db_ido/i2-db_ido.hpp" +#include "db_ido/dbconnection-ti.hpp" +#include "db_ido/dbobject.hpp" +#include "db_ido/dbquery.hpp" +#include "base/timer.hpp" +#include "base/ringbuffer.hpp" +#include <boost/thread/once.hpp> +#include <mutex> + +namespace icinga +{ + +/** + * A database connection. + * + * @ingroup db_ido + */ +class DbConnection : public ObjectImpl<DbConnection> +{ +public: + DECLARE_OBJECT(DbConnection); + + static void InitializeDbTimer(); + + virtual const char * GetLatestSchemaVersion() const noexcept = 0; + virtual const char * GetCompatSchemaVersion() const noexcept = 0; + + void SetConfigHash(const DbObject::Ptr& dbobj, const String& hash); + void SetConfigHash(const DbType::Ptr& type, const DbReference& objid, const String& hash); + String GetConfigHash(const DbObject::Ptr& dbobj) const; + String GetConfigHash(const DbType::Ptr& type, const DbReference& objid) const; + + void SetObjectID(const DbObject::Ptr& dbobj, const DbReference& dbref); + DbReference GetObjectID(const DbObject::Ptr& dbobj) const; + + void SetInsertID(const DbObject::Ptr& dbobj, const DbReference& dbref); + void SetInsertID(const DbType::Ptr& type, const DbReference& objid, const DbReference& dbref); + DbReference GetInsertID(const DbObject::Ptr& dbobj) const; + DbReference GetInsertID(const DbType::Ptr& type, const DbReference& objid) const; + + void SetObjectActive(const DbObject::Ptr& dbobj, bool active); + bool GetObjectActive(const DbObject::Ptr& dbobj) const; + + void ClearIDCache(); + + void SetConfigUpdate(const DbObject::Ptr& dbobj, bool hasupdate); + bool GetConfigUpdate(const DbObject::Ptr& dbobj) const; + + void SetStatusUpdate(const DbObject::Ptr& dbobj, bool hasupdate); + bool GetStatusUpdate(const DbObject::Ptr& dbobj) const; + + int GetQueryCount(RingBuffer::SizeType span); + virtual int GetPendingQueryCount() const = 0; + + void ValidateFailoverTimeout(const Lazy<double>& lvalue, const ValidationUtils& utils) final; + void ValidateCategories(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) final; + +protected: + void OnConfigLoaded() override; + void Start(bool runtimeCreated) override; + void Stop(bool runtimeRemoved) override; + void Resume() override; + void Pause() override; + + virtual void ExecuteQuery(const DbQuery& query) = 0; + virtual void ExecuteMultipleQueries(const std::vector<DbQuery>&) = 0; + virtual void ActivateObject(const DbObject::Ptr& dbobj) = 0; + virtual void DeactivateObject(const DbObject::Ptr& dbobj) = 0; + + virtual void CleanUpExecuteQuery(const String& table, const String& time_column, double max_age); + virtual void FillIDCache(const DbType::Ptr& type) = 0; + virtual void NewTransaction() = 0; + virtual void Disconnect() = 0; + + void UpdateObject(const ConfigObject::Ptr& object); + void UpdateAllObjects(); + + void PrepareDatabase(); + + void IncreaseQueryCount(); + + bool IsIDCacheValid() const; + void SetIDCacheValid(bool valid); + + void EnableActiveChangedHandler(); + + static void UpdateProgramStatus(); + + static int GetSessionToken(); + + void IncreasePendingQueries(int count); + void DecreasePendingQueries(int count); + + WorkQueue m_QueryQueue{10000000, 1, LogNotice}; + +private: + bool m_IDCacheValid{false}; + std::map<std::pair<DbType::Ptr, DbReference>, String> m_ConfigHashes; + std::map<DbObject::Ptr, DbReference> m_ObjectIDs; + std::map<std::pair<DbType::Ptr, DbReference>, DbReference> m_InsertIDs; + std::set<DbObject::Ptr> m_ActiveObjects; + std::set<DbObject::Ptr> m_ConfigUpdates; + std::set<DbObject::Ptr> m_StatusUpdates; + Timer::Ptr m_CleanUpTimer; + Timer::Ptr m_LogStatsTimer; + + double m_LogStatsTimeout; + + void CleanUpHandler(); + void LogStatsHandler(); + + static Timer::Ptr m_ProgramStatusTimer; + static boost::once_flag m_OnceFlag; + + static void InsertRuntimeVariable(const String& key, const Value& value); + + mutable std::mutex m_StatsMutex; + RingBuffer m_QueryStats{15 * 60}; + bool m_ActiveChangedHandler{false}; + + RingBuffer m_InputQueries{10}; + RingBuffer m_OutputQueries{10}; + Atomic<uint_fast64_t> m_PendingQueries{0}; +}; + +struct database_error : virtual std::exception, virtual boost::exception { }; + +struct errinfo_database_query_; +typedef boost::error_info<struct errinfo_database_query_, std::string> errinfo_database_query; + +} + +#endif /* DBCONNECTION_H */ diff --git a/lib/db_ido/dbconnection.ti b/lib/db_ido/dbconnection.ti new file mode 100644 index 0000000..ad02b40 --- /dev/null +++ b/lib/db_ido/dbconnection.ti @@ -0,0 +1,82 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbquery.hpp" +#include "base/configobject.hpp" + +library db_ido; + +namespace icinga +{ + +abstract class DbConnection : ConfigObject +{ + [config] String table_prefix { + default {{{ return "icinga_"; }}} + }; + + [config, required] Dictionary::Ptr cleanup { + default {{{ return new Dictionary(); }}} + }; + + [config] Array::Ptr categories { + default {{{ + return new Array({ + "DbCatConfig", + "DbCatState", + "DbCatAcknowledgement", + "DbCatComment", + "DbCatDowntime", + "DbCatEventHandler", + "DbCatFlapping", + "DbCatNotification", + "DbCatProgramStatus", + "DbCatRetention", + "DbCatStateHistory" + }); + }}} + }; + [no_user_view, no_user_modify] int categories_filter_real (CategoryFilter); + + [config] bool enable_ha { + default {{{ return true; }}} + }; + + [config] double failover_timeout { + default {{{ return 30; }}} + }; + + [state, no_user_modify] double last_failover; + + [no_user_modify] String schema_version; + [no_user_modify] bool connected; + [no_user_modify] bool should_connect { + default {{{ return true; }}} + }; +}; + + +validator DbConnection { + Dictionary cleanup { + Number acknowledgements_age; + Number commenthistory_age; + Number contactnotifications_age; + Number contactnotificationmethods_age; + Number downtimehistory_age; + Number eventhandlers_age; + Number externalcommands_age; + Number flappinghistory_age; + Number hostchecks_age; + Number logentries_age; + Number notifications_age; + Number processevents_age; + Number statehistory_age; + Number servicechecks_age; + Number systemcommands_age; + }; + + Array categories { + String "*"; + }; +}; + +} diff --git a/lib/db_ido/dbevents.cpp b/lib/db_ido/dbevents.cpp new file mode 100644 index 0000000..8358824 --- /dev/null +++ b/lib/db_ido/dbevents.cpp @@ -0,0 +1,1884 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbevents.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/initialize.hpp" +#include "base/configtype.hpp" +#include "base/utility.hpp" +#include "base/logger.hpp" +#include "remote/endpoint.hpp" +#include "icinga/notification.hpp" +#include "icinga/checkcommand.hpp" +#include "icinga/eventcommand.hpp" +#include "icinga/externalcommandprocessor.hpp" +#include "icinga/compatutility.hpp" +#include "icinga/pluginutility.hpp" +#include "icinga/icingaapplication.hpp" +#include <boost/algorithm/string/join.hpp> +#include <utility> + +using namespace icinga; + +INITIALIZE_ONCE(&DbEvents::StaticInitialize); + +void DbEvents::StaticInitialize() +{ + /* Status */ + Comment::OnCommentAdded.connect([](const Comment::Ptr& comment) { DbEvents::AddComment(comment); }); + Comment::OnCommentRemoved.connect([](const Comment::Ptr& comment) { DbEvents::RemoveComment(comment); }); + Downtime::OnDowntimeAdded.connect([](const Downtime::Ptr& downtime) { DbEvents::AddDowntime(downtime); }); + Downtime::OnDowntimeRemoved.connect([](const Downtime::Ptr& downtime) { DbEvents::RemoveDowntime(downtime); }); + Downtime::OnDowntimeTriggered.connect([](const Downtime::Ptr& downtime) { DbEvents::TriggerDowntime(downtime); }); + Checkable::OnAcknowledgementSet.connect([](const Checkable::Ptr& checkable, const String&, const String&, + AcknowledgementType type, bool, bool, double, double, const MessageOrigin::Ptr&) { + DbEvents::AddAcknowledgement(checkable, type); + }); + Checkable::OnAcknowledgementCleared.connect([](const Checkable::Ptr& checkable, const String&, double, const MessageOrigin::Ptr&) { + DbEvents::RemoveAcknowledgement(checkable); + }); + + Checkable::OnNextCheckUpdated.connect([](const Checkable::Ptr& checkable) { NextCheckUpdatedHandler(checkable); }); + Checkable::OnFlappingChanged.connect([](const Checkable::Ptr& checkable, const Value&) { FlappingChangedHandler(checkable); }); + Checkable::OnNotificationSentToAllUsers.connect([](const Notification::Ptr& notification, const Checkable::Ptr& checkable, + const std::set<User::Ptr>&, const NotificationType&, const CheckResult::Ptr&, const String&, const String&, + const MessageOrigin::Ptr&) { + DbEvents::LastNotificationChangedHandler(notification, checkable); + }); + + Checkable::OnEnableActiveChecksChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::EnableActiveChecksChangedHandler(checkable); + }); + Checkable::OnEnablePassiveChecksChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::EnablePassiveChecksChangedHandler(checkable); + }); + Checkable::OnEnableNotificationsChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::EnableNotificationsChangedHandler(checkable); + }); + Checkable::OnEnablePerfdataChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::EnablePerfdataChangedHandler(checkable); + }); + Checkable::OnEnableFlappingChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::EnableFlappingChangedHandler(checkable); + }); + + Checkable::OnReachabilityChanged.connect([](const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, + std::set<Checkable::Ptr> children, const MessageOrigin::Ptr&) { + DbEvents::ReachabilityChangedHandler(checkable, cr, std::move(children)); + }); + + /* History */ + Comment::OnCommentAdded.connect([](const Comment::Ptr& comment) { AddCommentHistory(comment); }); + Downtime::OnDowntimeAdded.connect([](const Downtime::Ptr& downtime) { AddDowntimeHistory(downtime); }); + Checkable::OnAcknowledgementSet.connect([](const Checkable::Ptr& checkable, const String& author, const String& comment, + AcknowledgementType type, bool notify, bool, double expiry, double, const MessageOrigin::Ptr&) { + DbEvents::AddAcknowledgementHistory(checkable, author, comment, type, notify, expiry); + }); + + Checkable::OnNotificationSentToAllUsers.connect([](const Notification::Ptr& notification, const Checkable::Ptr& checkable, + const std::set<User::Ptr>& users, const NotificationType& type, const CheckResult::Ptr& cr, const String& author, + const String& text, const MessageOrigin::Ptr&) { + DbEvents::AddNotificationHistory(notification, checkable, users, type, cr, author, text); + }); + + Checkable::OnStateChange.connect([](const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type, const MessageOrigin::Ptr&) { + DbEvents::AddStateChangeHistory(checkable, cr, type); + }); + + Checkable::OnNewCheckResult.connect([](const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin::Ptr&) { + DbEvents::AddCheckResultLogHistory(checkable, cr); + }); + Checkable::OnNotificationSentToUser.connect([](const Notification::Ptr& notification, const Checkable::Ptr& checkable, + const User::Ptr& users, const NotificationType& type, const CheckResult::Ptr& cr, const String& author, const String& text, + const String&, const MessageOrigin::Ptr&) { + DbEvents::AddNotificationSentLogHistory(notification, checkable, users, type, cr, author, text); + }); + Checkable::OnFlappingChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::AddFlappingChangedLogHistory(checkable); + }); + Checkable::OnEnableFlappingChanged.connect([](const Checkable::Ptr& checkable, const Value&) { + DbEvents::AddEnableFlappingChangedLogHistory(checkable); + }); + Downtime::OnDowntimeTriggered.connect([](const Downtime::Ptr& downtime) { DbEvents::AddTriggerDowntimeLogHistory(downtime); }); + Downtime::OnDowntimeRemoved.connect([](const Downtime::Ptr& downtime) { DbEvents::AddRemoveDowntimeLogHistory(downtime); }); + + Checkable::OnFlappingChanged.connect([](const Checkable::Ptr& checkable, const Value&) { DbEvents::AddFlappingChangedHistory(checkable); }); + Checkable::OnEnableFlappingChanged.connect([](const Checkable::Ptr& checkable, const Value&) { DbEvents::AddEnableFlappingChangedHistory(checkable); }); + Checkable::OnNewCheckResult.connect([](const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin::Ptr&) { + DbEvents::AddCheckableCheckHistory(checkable, cr); + }); + + Checkable::OnEventCommandExecuted.connect([](const Checkable::Ptr& checkable) { DbEvents::AddEventHandlerHistory(checkable); }); + + ExternalCommandProcessor::OnNewExternalCommand.connect([](double time, const String& command, const std::vector<String>& arguments) { + DbEvents::AddExternalCommandHistory(time, command, arguments); + }); +} + +/* check events */ +void DbEvents::NextCheckUpdatedHandler(const Checkable::Ptr& checkable) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query1; + query1.WhereCriteria = new Dictionary(); + + if (service) { + query1.Table = "servicestatus"; + query1.WhereCriteria->Set("service_object_id", service); + } else { + query1.Table = "hoststatus"; + query1.WhereCriteria->Set("host_object_id", host); + } + + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + query1.StatusUpdate = true; + query1.Object = DbObject::GetOrCreateByObject(checkable); + + query1.Fields = new Dictionary({ + { "next_check", DbValue::FromTimestamp(checkable->GetNextCheck()) } + }); + + DbObject::OnQuery(query1); +} + +void DbEvents::FlappingChangedHandler(const Checkable::Ptr& checkable) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query1; + query1.WhereCriteria = new Dictionary(); + + if (service) { + query1.Table = "servicestatus"; + query1.WhereCriteria->Set("service_object_id", service); + } else { + query1.Table = "hoststatus"; + query1.WhereCriteria->Set("host_object_id", host); + } + + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + query1.StatusUpdate = true; + query1.Object = DbObject::GetOrCreateByObject(checkable); + + Dictionary::Ptr fields1 = new Dictionary(); + fields1->Set("is_flapping", checkable->IsFlapping()); + fields1->Set("percent_state_change", checkable->GetFlappingCurrent()); + + query1.Fields = new Dictionary({ + { "is_flapping", checkable->IsFlapping() }, + { "percent_state_change", checkable->GetFlappingCurrent() } + }); + + query1.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + DbObject::OnQuery(query1); +} + +void DbEvents::LastNotificationChangedHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable) +{ + std::pair<unsigned long, unsigned long> now_bag = ConvertTimestamp(Utility::GetTime()); + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(notification->GetNextNotification()); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query1; + query1.WhereCriteria = new Dictionary(); + + if (service) { + query1.Table = "servicestatus"; + query1.WhereCriteria->Set("service_object_id", service); + } else { + query1.Table = "hoststatus"; + query1.WhereCriteria->Set("host_object_id", host); + } + + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + query1.StatusUpdate = true; + query1.Object = DbObject::GetOrCreateByObject(checkable); + + query1.Fields = new Dictionary({ + { "last_notification", DbValue::FromTimestamp(now_bag.first) }, + { "next_notification", DbValue::FromTimestamp(timeBag.first) }, + { "current_notification_number", notification->GetNotificationNumber() } + }); + + query1.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + DbObject::OnQuery(query1); +} + +void DbEvents::ReachabilityChangedHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, std::set<Checkable::Ptr> children) +{ + int is_reachable = 0; + + if (cr->GetState() == ServiceOK) + is_reachable = 1; + + for (const Checkable::Ptr& child : children) { + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(child); + + DbQuery query1; + query1.WhereCriteria = new Dictionary(); + + if (service) { + query1.Table = "servicestatus"; + query1.WhereCriteria->Set("service_object_id", service); + } else { + query1.Table = "hoststatus"; + query1.WhereCriteria->Set("host_object_id", host); + } + + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + query1.StatusUpdate = true; + query1.Object = DbObject::GetOrCreateByObject(child); + + query1.Fields = new Dictionary({ + { "is_reachable", is_reachable } + }); + + query1.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + DbObject::OnQuery(query1); + } +} + +/* enable changed events */ +void DbEvents::EnableActiveChecksChangedHandler(const Checkable::Ptr& checkable) +{ + EnableChangedHandlerInternal(checkable, "active_checks_enabled", checkable->GetEnableActiveChecks()); +} + +void DbEvents::EnablePassiveChecksChangedHandler(const Checkable::Ptr& checkable) +{ + EnableChangedHandlerInternal(checkable, "passive_checks_enabled", checkable->GetEnablePassiveChecks()); +} + +void DbEvents::EnableNotificationsChangedHandler(const Checkable::Ptr& checkable) +{ + EnableChangedHandlerInternal(checkable, "notifications_enabled", checkable->GetEnableNotifications()); +} + +void DbEvents::EnablePerfdataChangedHandler(const Checkable::Ptr& checkable) +{ + EnableChangedHandlerInternal(checkable, "process_performance_data", checkable->GetEnablePerfdata()); +} + +void DbEvents::EnableFlappingChangedHandler(const Checkable::Ptr& checkable) +{ + EnableChangedHandlerInternal(checkable, "flap_detection_enabled", checkable->GetEnableFlapping()); +} + +void DbEvents::EnableChangedHandlerInternal(const Checkable::Ptr& checkable, const String& fieldName, bool enabled) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query1; + query1.WhereCriteria = new Dictionary(); + + if (service) { + query1.Table = "servicestatus"; + query1.WhereCriteria->Set("service_object_id", service); + } else { + query1.Table = "hoststatus"; + query1.WhereCriteria->Set("host_object_id", host); + } + + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + query1.StatusUpdate = true; + query1.Object = DbObject::GetOrCreateByObject(checkable); + + query1.Fields = new Dictionary({ + { fieldName, enabled } + }); + + query1.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + DbObject::OnQuery(query1); +} + + +/* comments */ +void DbEvents::AddComments(const Checkable::Ptr& checkable) +{ + std::set<Comment::Ptr> comments = checkable->GetComments(); + + std::vector<DbQuery> queries; + + for (const Comment::Ptr& comment : comments) { + AddCommentInternal(queries, comment, false); + } + + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::AddComment(const Comment::Ptr& comment) +{ + std::vector<DbQuery> queries; + AddCommentInternal(queries, comment, false); + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::AddCommentHistory(const Comment::Ptr& comment) +{ + std::vector<DbQuery> queries; + AddCommentInternal(queries, comment, true); + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::AddCommentInternal(std::vector<DbQuery>& queries, const Comment::Ptr& comment, bool historical) +{ + Checkable::Ptr checkable = comment->GetCheckable(); + + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(comment->GetEntryTime()); + + Dictionary::Ptr fields1 = new Dictionary(); + fields1->Set("entry_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("entry_time_usec", timeBag.second); + fields1->Set("entry_type", comment->GetEntryType()); + fields1->Set("object_id", checkable); + + int commentType = 0; + + if (checkable->GetReflectionType() == Host::TypeInstance) + commentType = 2; + else if (checkable->GetReflectionType() == Service::TypeInstance) + commentType = 1; + else { + return; + } + + fields1->Set("comment_type", commentType); + fields1->Set("internal_comment_id", comment->GetLegacyId()); + fields1->Set("name", comment->GetName()); + fields1->Set("comment_time", DbValue::FromTimestamp(timeBag.first)); /* same as entry_time */ + fields1->Set("author_name", comment->GetAuthor()); + fields1->Set("comment_data", comment->GetText()); + fields1->Set("is_persistent", comment->GetPersistent()); + fields1->Set("comment_source", 1); /* external */ + fields1->Set("expires", (comment->GetExpireTime() > 0)); + fields1->Set("expiration_time", DbValue::FromTimestamp(comment->GetExpireTime())); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + DbQuery query1; + + if (!historical) { + query1.Table = "comments"; + query1.Type = DbQueryInsert | DbQueryUpdate; + + fields1->Set("session_token", 0); /* DbConnection class fills in real ID */ + + query1.WhereCriteria = new Dictionary({ + { "object_id", checkable }, + { "name", comment->GetName() }, + { "entry_time", DbValue::FromTimestamp(timeBag.first) } + }); + } else { + query1.Table = "commenthistory"; + query1.Type = DbQueryInsert; + } + + query1.Category = DbCatComment; + query1.Fields = fields1; + queries.emplace_back(std::move(query1)); +} + +void DbEvents::RemoveComment(const Comment::Ptr& comment) +{ + std::vector<DbQuery> queries; + RemoveCommentInternal(queries, comment); + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::RemoveCommentInternal(std::vector<DbQuery>& queries, const Comment::Ptr& comment) +{ + Checkable::Ptr checkable = comment->GetCheckable(); + + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(comment->GetEntryTime()); + + /* Status */ + DbQuery query1; + query1.Table = "comments"; + query1.Type = DbQueryDelete; + query1.Category = DbCatComment; + + query1.WhereCriteria = new Dictionary({ + { "object_id", checkable }, + { "entry_time", DbValue::FromTimestamp(timeBag.first) }, + { "name", comment->GetName() } + }); + + queries.emplace_back(std::move(query1)); + + /* History - update deletion time for service/host */ + std::pair<unsigned long, unsigned long> timeBagNow = ConvertTimestamp(Utility::GetTime()); + + DbQuery query2; + query2.Table = "commenthistory"; + query2.Type = DbQueryUpdate; + query2.Category = DbCatComment; + + query2.Fields = new Dictionary({ + { "deletion_time", DbValue::FromTimestamp(timeBagNow.first) }, + { "deletion_time_usec", timeBagNow.second } + }); + + query2.WhereCriteria = new Dictionary({ + { "object_id", checkable }, + { "entry_time", DbValue::FromTimestamp(timeBag.first) }, + { "name", comment->GetName() } + }); + + queries.emplace_back(std::move(query2)); +} + +/* downtimes */ +void DbEvents::AddDowntimes(const Checkable::Ptr& checkable) +{ + std::set<Downtime::Ptr> downtimes = checkable->GetDowntimes(); + + std::vector<DbQuery> queries; + + for (const Downtime::Ptr& downtime : downtimes) { + AddDowntimeInternal(queries, downtime, false); + } + + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::AddDowntime(const Downtime::Ptr& downtime) +{ + std::vector<DbQuery> queries; + AddDowntimeInternal(queries, downtime, false); + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::AddDowntimeHistory(const Downtime::Ptr& downtime) +{ + std::vector<DbQuery> queries; + AddDowntimeInternal(queries, downtime, true); + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::AddDowntimeInternal(std::vector<DbQuery>& queries, const Downtime::Ptr& downtime, bool historical) +{ + Checkable::Ptr checkable = downtime->GetCheckable(); + + Dictionary::Ptr fields1 = new Dictionary(); + fields1->Set("entry_time", DbValue::FromTimestamp(downtime->GetEntryTime())); + fields1->Set("object_id", checkable); + + int downtimeType = 0; + + if (checkable->GetReflectionType() == Host::TypeInstance) + downtimeType = 2; + else if (checkable->GetReflectionType() == Service::TypeInstance) + downtimeType = 1; + else { + return; + } + + fields1->Set("downtime_type", downtimeType); + fields1->Set("internal_downtime_id", downtime->GetLegacyId()); + fields1->Set("author_name", downtime->GetAuthor()); + fields1->Set("comment_data", downtime->GetComment()); + fields1->Set("triggered_by_id", Downtime::GetByName(downtime->GetTriggeredBy())); + fields1->Set("is_fixed", downtime->GetFixed()); + fields1->Set("duration", downtime->GetDuration()); + fields1->Set("scheduled_start_time", DbValue::FromTimestamp(downtime->GetStartTime())); + fields1->Set("scheduled_end_time", DbValue::FromTimestamp(downtime->GetEndTime())); + fields1->Set("name", downtime->GetName()); + + /* flexible downtimes are started at trigger time */ + if (downtime->GetFixed()) { + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(downtime->GetStartTime()); + + fields1->Set("actual_start_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("actual_start_time_usec", timeBag.second); + fields1->Set("was_started", ((downtime->GetStartTime() <= Utility::GetTime()) ? 1 : 0)); + } + + fields1->Set("is_in_effect", downtime->IsInEffect()); + fields1->Set("trigger_time", DbValue::FromTimestamp(downtime->GetTriggerTime())); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + DbQuery query1; + + if (!historical) { + query1.Table = "scheduleddowntime"; + query1.Type = DbQueryInsert | DbQueryUpdate; + + fields1->Set("session_token", 0); /* DbConnection class fills in real ID */ + + query1.WhereCriteria = new Dictionary({ + { "object_id", checkable }, + { "name", downtime->GetName() }, + { "entry_time", DbValue::FromTimestamp(downtime->GetEntryTime()) } + }); + } else { + query1.Table = "downtimehistory"; + query1.Type = DbQueryInsert; + } + + query1.Category = DbCatDowntime; + query1.Fields = fields1; + queries.emplace_back(std::move(query1)); + + /* host/service status */ + if (!historical) { + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query2; + query2.WhereCriteria = new Dictionary(); + + if (service) { + query2.Table = "servicestatus"; + query2.WhereCriteria->Set("service_object_id", service); + } else { + query2.Table = "hoststatus"; + query2.WhereCriteria->Set("host_object_id", host); + } + + query2.Type = DbQueryUpdate; + query2.Category = DbCatState; + query2.StatusUpdate = true; + query2.Object = DbObject::GetOrCreateByObject(checkable); + + Dictionary::Ptr fields2 = new Dictionary(); + fields2->Set("scheduled_downtime_depth", checkable->GetDowntimeDepth()); + + query2.Fields = fields2; + query2.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + queries.emplace_back(std::move(query2)); + } +} + +void DbEvents::RemoveDowntime(const Downtime::Ptr& downtime) +{ + std::vector<DbQuery> queries; + RemoveDowntimeInternal(queries, downtime); + DbObject::OnMultipleQueries(queries); +} + +void DbEvents::RemoveDowntimeInternal(std::vector<DbQuery>& queries, const Downtime::Ptr& downtime) +{ + Checkable::Ptr checkable = downtime->GetCheckable(); + + /* Status */ + DbQuery query1; + query1.Table = "scheduleddowntime"; + query1.Type = DbQueryDelete; + query1.Category = DbCatDowntime; + query1.WhereCriteria = new Dictionary(); + + query1.WhereCriteria->Set("object_id", checkable); + query1.WhereCriteria->Set("entry_time", DbValue::FromTimestamp(downtime->GetEntryTime())); + query1.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + query1.WhereCriteria->Set("scheduled_start_time", DbValue::FromTimestamp(downtime->GetStartTime())); + query1.WhereCriteria->Set("scheduled_end_time", DbValue::FromTimestamp(downtime->GetEndTime())); + query1.WhereCriteria->Set("name", downtime->GetName()); + queries.emplace_back(std::move(query1)); + + /* History - update actual_end_time, was_cancelled for service (and host in case) */ + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + DbQuery query3; + query3.Table = "downtimehistory"; + query3.Type = DbQueryUpdate; + query3.Category = DbCatDowntime; + + Dictionary::Ptr fields3 = new Dictionary(); + fields3->Set("was_cancelled", downtime->GetWasCancelled() ? 1 : 0); + + if (downtime->GetFixed() || (!downtime->GetFixed() && downtime->GetTriggerTime() > 0)) { + fields3->Set("actual_end_time", DbValue::FromTimestamp(timeBag.first)); + fields3->Set("actual_end_time_usec", timeBag.second); + } + + fields3->Set("is_in_effect", 0); + query3.Fields = fields3; + + query3.WhereCriteria = new Dictionary({ + { "object_id", checkable }, + { "entry_time", DbValue::FromTimestamp(downtime->GetEntryTime()) }, + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "scheduled_start_time", DbValue::FromTimestamp(downtime->GetStartTime()) }, + { "scheduled_end_time", DbValue::FromTimestamp(downtime->GetEndTime()) }, + { "name", downtime->GetName() } + }); + + queries.emplace_back(std::move(query3)); + + /* host/service status */ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query4; + query4.WhereCriteria = new Dictionary(); + + if (service) { + query4.Table = "servicestatus"; + query4.WhereCriteria->Set("service_object_id", service); + } else { + query4.Table = "hoststatus"; + query4.WhereCriteria->Set("host_object_id", host); + } + + query4.Type = DbQueryUpdate; + query4.Category = DbCatState; + query4.StatusUpdate = true; + query4.Object = DbObject::GetOrCreateByObject(checkable); + + Dictionary::Ptr fields4 = new Dictionary(); + fields4->Set("scheduled_downtime_depth", checkable->GetDowntimeDepth()); + + query4.Fields = fields4; + query4.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + queries.emplace_back(std::move(query4)); +} + +void DbEvents::TriggerDowntime(const Downtime::Ptr& downtime) +{ + Checkable::Ptr checkable = downtime->GetCheckable(); + + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + /* Status */ + DbQuery query1; + query1.Table = "scheduleddowntime"; + query1.Type = DbQueryUpdate; + query1.Category = DbCatDowntime; + + query1.Fields = new Dictionary({ + { "was_started", 1 }, + { "actual_start_time", DbValue::FromTimestamp(timeBag.first) }, + { "actual_start_time_usec", timeBag.second }, + { "is_in_effect", (downtime->IsInEffect() ? 1 : 0) }, + { "trigger_time", DbValue::FromTimestamp(downtime->GetTriggerTime()) }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + query1.WhereCriteria = new Dictionary({ + { "object_id", checkable }, + { "entry_time", DbValue::FromTimestamp(downtime->GetEntryTime()) }, + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "scheduled_start_time", DbValue::FromTimestamp(downtime->GetStartTime()) }, + { "scheduled_end_time", DbValue::FromTimestamp(downtime->GetEndTime()) }, + { "name", downtime->GetName() } + }); + + DbObject::OnQuery(query1); + + /* History - downtime was started for service (and host in case) */ + DbQuery query3; + query3.Table = "downtimehistory"; + query3.Type = DbQueryUpdate; + query3.Category = DbCatDowntime; + + query3.Fields = new Dictionary({ + { "was_started", 1 }, + { "is_in_effect", 1 }, + { "actual_start_time", DbValue::FromTimestamp(timeBag.first) }, + { "actual_start_time_usec", timeBag.second }, + { "trigger_time", DbValue::FromTimestamp(downtime->GetTriggerTime()) } + }); + + query3.WhereCriteria = query1.WhereCriteria; + + DbObject::OnQuery(query3); + + /* host/service status */ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query4; + query4.WhereCriteria = new Dictionary(); + + if (service) { + query4.Table = "servicestatus"; + query4.WhereCriteria->Set("service_object_id", service); + } else { + query4.Table = "hoststatus"; + query4.WhereCriteria->Set("host_object_id", host); + } + + query4.Type = DbQueryUpdate; + query4.Category = DbCatState; + query4.StatusUpdate = true; + query4.Object = DbObject::GetOrCreateByObject(checkable); + + query4.Fields = new Dictionary({ + { "scheduled_downtime_depth", checkable->GetDowntimeDepth() } + }); + query4.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + DbObject::OnQuery(query4); +} + +/* acknowledgements */ +void DbEvents::AddAcknowledgementHistory(const Checkable::Ptr& checkable, const String& author, const String& comment, + AcknowledgementType type, bool notify, double expiry) +{ + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + DbQuery query1; + query1.Table = "acknowledgements"; + query1.Type = DbQueryInsert; + query1.Category = DbCatAcknowledgement; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr fields1 = new Dictionary(); + + fields1->Set("entry_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("entry_time_usec", timeBag.second); + fields1->Set("acknowledgement_type", type); + fields1->Set("object_id", checkable); + fields1->Set("author_name", author); + fields1->Set("comment_data", comment); + fields1->Set("persistent_comment", 1); + fields1->Set("notify_contacts", notify); + fields1->Set("is_sticky", type == AcknowledgementSticky); + fields1->Set("end_time", DbValue::FromTimestamp(expiry)); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + if (service) + fields1->Set("state", service->GetState()); + else + fields1->Set("state", GetHostState(host)); + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +void DbEvents::AddAcknowledgement(const Checkable::Ptr& checkable, AcknowledgementType type) +{ + AddAcknowledgementInternal(checkable, type, true); +} + +void DbEvents::RemoveAcknowledgement(const Checkable::Ptr& checkable) +{ + AddAcknowledgementInternal(checkable, AcknowledgementNone, false); +} + +void DbEvents::AddAcknowledgementInternal(const Checkable::Ptr& checkable, AcknowledgementType type, bool add) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query1; + query1.WhereCriteria = new Dictionary(); + + if (service) { + query1.Table = "servicestatus"; + query1.WhereCriteria->Set("service_object_id", service); + } else { + query1.Table = "hoststatus"; + query1.WhereCriteria->Set("host_object_id", host); + } + + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + query1.StatusUpdate = true; + query1.Object = DbObject::GetOrCreateByObject(checkable); + + query1.Fields = new Dictionary({ + { "acknowledgement_type", type }, + { "problem_has_been_acknowledged", add ? 1 : 0 } + }); + + query1.WhereCriteria->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + DbObject::OnQuery(query1); +} + +/* notifications */ +void DbEvents::AddNotificationHistory(const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users, NotificationType type, + const CheckResult::Ptr& cr, const String& author, const String& text) +{ + /* NotificationInsertID has to be tracked per IDO instance, therefore the OnQuery and OnMultipleQueries signals + * cannot be called directly as all IDO instances would insert rows with the same ID which is (most likely) only + * correct in one database. Instead, pass a lambda which generates the queries with new DbValue for + * NotificationInsertID each IDO instance. + */ + DbObject::OnMakeQueries([&checkable, &users, &type, &cr](const DbObject::QueryCallbacks& callbacks) { + /* start and end happen at the same time */ + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + DbQuery query1; + query1.Table = "notifications"; + query1.Type = DbQueryInsert; + query1.Category = DbCatNotification; + query1.NotificationInsertID = new DbValue(DbValueObjectInsertID, -1); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr fields1 = new Dictionary(); + fields1->Set("notification_type", 1); /* service */ + fields1->Set("notification_reason", MapNotificationReasonType(type)); + fields1->Set("object_id", checkable); + fields1->Set("start_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("start_time_usec", timeBag.second); + fields1->Set("end_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("end_time_usec", timeBag.second); + + if (service) + fields1->Set("state", service->GetState()); + else + fields1->Set("state", GetHostState(host)); + + if (cr) { + fields1->Set("output", CompatUtility::GetCheckResultOutput(cr)); + fields1->Set("long_output", CompatUtility::GetCheckResultLongOutput(cr)); + } + + fields1->Set("escalated", 0); + fields1->Set("contacts_notified", static_cast<long>(users.size())); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + callbacks.Query(query1); + + std::vector<DbQuery> queries; + + for (const User::Ptr& user : users) { + DbQuery query2; + query2.Table = "contactnotifications"; + query2.Type = DbQueryInsert; + query2.Category = DbCatNotification; + + query2.Fields = new Dictionary({ + { "contact_object_id", user }, + { "start_time", DbValue::FromTimestamp(timeBag.first) }, + { "start_time_usec", timeBag.second }, + { "end_time", DbValue::FromTimestamp(timeBag.first) }, + { "end_time_usec", timeBag.second }, + { "notification_id", query1.NotificationInsertID }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + queries.emplace_back(std::move(query2)); + } + + callbacks.MultipleQueries(queries); + }); +} + +/* statehistory */ +void DbEvents::AddStateChangeHistory(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type) +{ + double ts = cr->GetExecutionEnd(); + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(ts); + + DbQuery query1; + query1.Table = "statehistory"; + query1.Type = DbQueryInsert; + query1.Category = DbCatStateHistory; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr fields1 = new Dictionary(); + fields1->Set("state_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("state_time_usec", timeBag.second); + fields1->Set("object_id", checkable); + fields1->Set("state_change", 1); /* service */ + fields1->Set("state_type", checkable->GetStateType()); + fields1->Set("current_check_attempt", checkable->GetCheckAttempt()); + fields1->Set("max_check_attempts", checkable->GetMaxCheckAttempts()); + + if (service) { + fields1->Set("state", service->GetState()); + fields1->Set("last_state", service->GetLastState()); + fields1->Set("last_hard_state", service->GetLastHardState()); + } else { + fields1->Set("state", GetHostState(host)); + fields1->Set("last_state", host->GetLastState()); + fields1->Set("last_hard_state", host->GetLastHardState()); + } + + if (cr) { + fields1->Set("output", CompatUtility::GetCheckResultOutput(cr)); + fields1->Set("long_output", CompatUtility::GetCheckResultLongOutput(cr)); + fields1->Set("check_source", cr->GetCheckSource()); + } + + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +/* logentries */ +void DbEvents::AddCheckResultLogHistory(const Checkable::Ptr& checkable, const CheckResult::Ptr &cr) +{ + if (!cr) + return; + + Dictionary::Ptr varsBefore = cr->GetVarsBefore(); + Dictionary::Ptr varsAfter = cr->GetVarsAfter(); + + if (varsBefore && varsAfter) { + if (varsBefore->Get("state") == varsAfter->Get("state") && + varsBefore->Get("state_type") == varsAfter->Get("state_type") && + varsBefore->Get("attempt") == varsAfter->Get("attempt") && + varsBefore->Get("reachable") == varsAfter->Get("reachable")) + return; /* Nothing changed, ignore this checkresult. */ + } + + LogEntryType type; + String output = CompatUtility::GetCheckResultOutput(cr); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + std::ostringstream msgbuf; + + if (service) { + msgbuf << "SERVICE ALERT: " + << host->GetName() << ";" + << service->GetShortName() << ";" + << Service::StateToString(service->GetState()) << ";" + << Service::StateTypeToString(service->GetStateType()) << ";" + << service->GetCheckAttempt() << ";" + << output << "" + << ""; + + switch (service->GetState()) { + case ServiceOK: + type = LogEntryTypeServiceOk; + break; + case ServiceUnknown: + type = LogEntryTypeServiceUnknown; + break; + case ServiceWarning: + type = LogEntryTypeServiceWarning; + break; + case ServiceCritical: + type = LogEntryTypeServiceCritical; + break; + default: + Log(LogCritical, "DbEvents") + << "Unknown service state: " << service->GetState(); + return; + } + } else { + msgbuf << "HOST ALERT: " + << host->GetName() << ";" + << GetHostStateString(host) << ";" + << Host::StateTypeToString(host->GetStateType()) << ";" + << host->GetCheckAttempt() << ";" + << output << "" + << ""; + + switch (host->GetState()) { + case HostUp: + type = LogEntryTypeHostUp; + break; + case HostDown: + type = LogEntryTypeHostDown; + break; + default: + Log(LogCritical, "DbEvents") + << "Unknown host state: " << host->GetState(); + return; + } + + if (!host->IsReachable()) + type = LogEntryTypeHostUnreachable; + } + + AddLogHistory(checkable, msgbuf.str(), type); +} + +void DbEvents::AddTriggerDowntimeLogHistory(const Downtime::Ptr& downtime) +{ + Checkable::Ptr checkable = downtime->GetCheckable(); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + std::ostringstream msgbuf; + + if (service) { + msgbuf << "SERVICE DOWNTIME ALERT: " + << host->GetName() << ";" + << service->GetShortName() << ";" + << "STARTED" << "; " + << "Service has entered a period of scheduled downtime." + << ""; + } else { + msgbuf << "HOST DOWNTIME ALERT: " + << host->GetName() << ";" + << "STARTED" << "; " + << "Service has entered a period of scheduled downtime." + << ""; + } + + AddLogHistory(checkable, msgbuf.str(), LogEntryTypeInfoMessage); +} + +void DbEvents::AddRemoveDowntimeLogHistory(const Downtime::Ptr& downtime) +{ + Checkable::Ptr checkable = downtime->GetCheckable(); + + String downtimeOutput; + String downtimeStateStr; + + if (downtime->GetWasCancelled()) { + downtimeOutput = "Scheduled downtime for service has been cancelled."; + downtimeStateStr = "CANCELLED"; + } else { + downtimeOutput = "Service has exited from a period of scheduled downtime."; + downtimeStateStr = "STOPPED"; + } + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + std::ostringstream msgbuf; + + if (service) { + msgbuf << "SERVICE DOWNTIME ALERT: " + << host->GetName() << ";" + << service->GetShortName() << ";" + << downtimeStateStr << "; " + << downtimeOutput + << ""; + } else { + msgbuf << "HOST DOWNTIME ALERT: " + << host->GetName() << ";" + << downtimeStateStr << "; " + << downtimeOutput + << ""; + } + + AddLogHistory(checkable, msgbuf.str(), LogEntryTypeInfoMessage); +} + +void DbEvents::AddNotificationSentLogHistory(const Notification::Ptr& notification, const Checkable::Ptr& checkable, const User::Ptr& user, + NotificationType notification_type, const CheckResult::Ptr& cr, + const String& author, const String& comment_text) +{ + CheckCommand::Ptr commandObj = checkable->GetCheckCommand(); + + String checkCommandName; + + if (commandObj) + checkCommandName = commandObj->GetName(); + + String notificationTypeStr = Notification::NotificationTypeToStringCompat(notification_type); //TODO: Change that to our own types. + + String author_comment = ""; + if (notification_type == NotificationCustom || notification_type == NotificationAcknowledgement) { + author_comment = ";" + author + ";" + comment_text; + } + + if (!cr) + return; + + String output = CompatUtility::GetCheckResultOutput(cr); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + std::ostringstream msgbuf; + + if (service) { + msgbuf << "SERVICE NOTIFICATION: " + << user->GetName() << ";" + << host->GetName() << ";" + << service->GetShortName() << ";" + << notificationTypeStr << " " + << "(" << Service::StateToString(service->GetState()) << ");" + << checkCommandName << ";" + << output << author_comment + << ""; + } else { + msgbuf << "HOST NOTIFICATION: " + << user->GetName() << ";" + << host->GetName() << ";" + << notificationTypeStr << " " + << "(" << Host::StateToString(host->GetState()) << ");" + << checkCommandName << ";" + << output << author_comment + << ""; + } + + AddLogHistory(checkable, msgbuf.str(), LogEntryTypeHostNotification); +} + +void DbEvents::AddFlappingChangedLogHistory(const Checkable::Ptr& checkable) +{ + String flappingStateStr; + String flappingOutput; + + if (checkable->IsFlapping()) { + flappingOutput = "Service appears to have started flapping (" + Convert::ToString(checkable->GetFlappingCurrent()) + "% change >= " + Convert::ToString(checkable->GetFlappingThresholdHigh()) + "% threshold)"; + flappingStateStr = "STARTED"; + } else { + flappingOutput = "Service appears to have stopped flapping (" + Convert::ToString(checkable->GetFlappingCurrent()) + "% change < " + Convert::ToString(checkable->GetFlappingThresholdLow()) + "% threshold)"; + flappingStateStr = "STOPPED"; + } + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + std::ostringstream msgbuf; + + if (service) { + msgbuf << "SERVICE FLAPPING ALERT: " + << host->GetName() << ";" + << service->GetShortName() << ";" + << flappingStateStr << "; " + << flappingOutput + << ""; + } else { + msgbuf << "HOST FLAPPING ALERT: " + << host->GetName() << ";" + << flappingStateStr << "; " + << flappingOutput + << ""; + } + + AddLogHistory(checkable, msgbuf.str(), LogEntryTypeInfoMessage); +} + +void DbEvents::AddEnableFlappingChangedLogHistory(const Checkable::Ptr& checkable) +{ + if (!checkable->GetEnableFlapping()) + return; + + String flappingOutput = "Flap detection has been disabled"; + String flappingStateStr = "DISABLED"; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + std::ostringstream msgbuf; + + if (service) { + msgbuf << "SERVICE FLAPPING ALERT: " + << host->GetName() << ";" + << service->GetShortName() << ";" + << flappingStateStr << "; " + << flappingOutput + << ""; + } else { + msgbuf << "HOST FLAPPING ALERT: " + << host->GetName() << ";" + << flappingStateStr << "; " + << flappingOutput + << ""; + } + + AddLogHistory(checkable, msgbuf.str(), LogEntryTypeInfoMessage); +} + +void DbEvents::AddLogHistory(const Checkable::Ptr& checkable, const String& buffer, LogEntryType type) +{ + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + DbQuery query1; + query1.Table = "logentries"; + query1.Type = DbQueryInsert; + query1.Category = DbCatLog; + + Dictionary::Ptr fields1 = new Dictionary(); + + fields1->Set("logentry_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("entry_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("entry_time_usec", timeBag.second); + fields1->Set("object_id", checkable); + fields1->Set("logentry_type", type); + fields1->Set("logentry_data", buffer); + + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +/* flappinghistory */ +void DbEvents::AddFlappingChangedHistory(const Checkable::Ptr& checkable) +{ + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + DbQuery query1; + query1.Table = "flappinghistory"; + query1.Type = DbQueryInsert; + query1.Category = DbCatFlapping; + + Dictionary::Ptr fields1 = new Dictionary(); + + fields1->Set("event_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("event_time_usec", timeBag.second); + + if (checkable->IsFlapping()) + fields1->Set("event_type", 1000); + else { + fields1->Set("event_type", 1001); + fields1->Set("reason_type", 1); + } + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + fields1->Set("flapping_type", service ? 1 : 0); + fields1->Set("object_id", checkable); + fields1->Set("percent_state_change", checkable->GetFlappingCurrent()); + fields1->Set("low_threshold", checkable->GetFlappingThresholdLow()); + fields1->Set("high_threshold", checkable->GetFlappingThresholdHigh()); + + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +void DbEvents::AddEnableFlappingChangedHistory(const Checkable::Ptr& checkable) +{ + if (!checkable->GetEnableFlapping()) + return; + + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + DbQuery query1; + query1.Table = "flappinghistory"; + query1.Type = DbQueryInsert; + query1.Category = DbCatFlapping; + + Dictionary::Ptr fields1 = new Dictionary(); + + fields1->Set("event_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("event_time_usec", timeBag.second); + + fields1->Set("event_type", 1001); + fields1->Set("reason_type", 2); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + fields1->Set("flapping_type", service ? 1 : 0); + fields1->Set("object_id", checkable); + fields1->Set("percent_state_change", checkable->GetFlappingCurrent()); + fields1->Set("low_threshold", checkable->GetFlappingThresholdLow()); + fields1->Set("high_threshold", checkable->GetFlappingThresholdHigh()); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +/* servicechecks */ +void DbEvents::AddCheckableCheckHistory(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) +{ + if (!cr) + return; + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + DbQuery query1; + query1.Table = service ? "servicechecks" : "hostchecks"; + query1.Type = DbQueryInsert; + query1.Category = DbCatCheck; + + Dictionary::Ptr fields1 = new Dictionary(); + fields1->Set("check_type", !checkable->GetEnableActiveChecks()); /* 0 .. active, 1 .. passive */ + fields1->Set("current_check_attempt", checkable->GetCheckAttempt()); + fields1->Set("max_check_attempts", checkable->GetMaxCheckAttempts()); + fields1->Set("state_type", checkable->GetStateType()); + + double start = cr->GetExecutionStart(); + double end = cr->GetExecutionEnd(); + double executionTime = cr->CalculateExecutionTime(); + + std::pair<unsigned long, unsigned long> timeBagStart = ConvertTimestamp(start); + std::pair<unsigned long, unsigned long> timeBagEnd = ConvertTimestamp(end); + + fields1->Set("start_time", DbValue::FromTimestamp(timeBagStart.first)); + fields1->Set("start_time_usec", timeBagStart.second); + fields1->Set("end_time", DbValue::FromTimestamp(timeBagEnd.first)); + fields1->Set("end_time_usec", timeBagEnd.second); + fields1->Set("command_object_id", checkable->GetCheckCommand()); + fields1->Set("execution_time", executionTime); + fields1->Set("latency", cr->CalculateLatency()); + fields1->Set("return_code", cr->GetExitStatus()); + fields1->Set("perfdata", PluginUtility::FormatPerfdata(cr->GetPerformanceData())); + + fields1->Set("output", CompatUtility::GetCheckResultOutput(cr)); + fields1->Set("long_output", CompatUtility::GetCheckResultLongOutput(cr)); + fields1->Set("command_line", CompatUtility::GetCommandLine(checkable->GetCheckCommand())); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + if (service) { + fields1->Set("service_object_id", service); + fields1->Set("state", service->GetState()); + } else { + fields1->Set("host_object_id", host); + fields1->Set("state", GetHostState(host)); + } + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +/* eventhandlers */ +void DbEvents::AddEventHandlerHistory(const Checkable::Ptr& checkable) +{ + DbQuery query1; + query1.Table = "eventhandlers"; + query1.Type = DbQueryInsert; + query1.Category = DbCatEventHandler; + + Dictionary::Ptr fields1 = new Dictionary(); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + fields1->Set("object_id", checkable); + fields1->Set("state_type", checkable->GetStateType()); + fields1->Set("command_object_id", checkable->GetEventCommand()); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + if (service) { + fields1->Set("state", service->GetState()); + fields1->Set("eventhandler_type", 1); + } else { + fields1->Set("state", GetHostState(host)); + fields1->Set("eventhandler_type", 0); + } + + std::pair<unsigned long, unsigned long> timeBag = ConvertTimestamp(Utility::GetTime()); + + fields1->Set("start_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("start_time_usec", timeBag.second); + fields1->Set("end_time", DbValue::FromTimestamp(timeBag.first)); + fields1->Set("end_time_usec", timeBag.second); + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +/* externalcommands */ +void DbEvents::AddExternalCommandHistory(double time, const String& command, const std::vector<String>& arguments) +{ + DbQuery query1; + query1.Table = "externalcommands"; + query1.Type = DbQueryInsert; + query1.Category = DbCatExternalCommand; + + Dictionary::Ptr fields1 = new Dictionary(); + + fields1->Set("entry_time", DbValue::FromTimestamp(time)); + fields1->Set("command_type", MapExternalCommandType(command)); + fields1->Set("command_name", command); + fields1->Set("command_args", boost::algorithm::join(arguments, ";")); + fields1->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + Endpoint::Ptr endpoint = Endpoint::GetByName(IcingaApplication::GetInstance()->GetNodeName()); + + if (endpoint) + fields1->Set("endpoint_object_id", endpoint); + + query1.Fields = fields1; + DbObject::OnQuery(query1); +} + +int DbEvents::GetHostState(const Host::Ptr& host) +{ + int currentState = host->GetState(); + + if (currentState != HostUp && !host->IsReachable()) + currentState = 2; /* hardcoded compat state */ + + return currentState; +} + +String DbEvents::GetHostStateString(const Host::Ptr& host) +{ + if (host->GetState() != HostUp && !host->IsReachable()) + return "UNREACHABLE"; /* hardcoded compat state */ + + return Host::StateToString(host->GetState()); +} + +std::pair<unsigned long, unsigned long> DbEvents::ConvertTimestamp(double time) +{ + unsigned long time_sec = static_cast<long>(time); + unsigned long time_usec = (time - time_sec) * 1000 * 1000; + + return std::make_pair(time_sec, time_usec); +} + +int DbEvents::MapNotificationReasonType(NotificationType type) +{ + switch (type) { + case NotificationDowntimeStart: + return 5; + case NotificationDowntimeEnd: + return 6; + case NotificationDowntimeRemoved: + return 7; + case NotificationCustom: + return 8; + case NotificationAcknowledgement: + return 1; + case NotificationProblem: + return 0; + case NotificationRecovery: + return 0; + case NotificationFlappingStart: + return 2; + case NotificationFlappingEnd: + return 3; + default: + return 0; + } +} + +int DbEvents::MapExternalCommandType(const String& name) +{ + if (name == "NONE") + return 0; + if (name == "ADD_HOST_COMMENT") + return 1; + if (name == "DEL_HOST_COMMENT") + return 2; + if (name == "ADD_SVC_COMMENT") + return 3; + if (name == "DEL_SVC_COMMENT") + return 4; + if (name == "ENABLE_SVC_CHECK") + return 5; + if (name == "DISABLE_SVC_CHECK") + return 6; + if (name == "SCHEDULE_SVC_CHECK") + return 7; + if (name == "DELAY_SVC_NOTIFICATION") + return 9; + if (name == "DELAY_HOST_NOTIFICATION") + return 10; + if (name == "DISABLE_NOTIFICATIONS") + return 11; + if (name == "ENABLE_NOTIFICATIONS") + return 12; + if (name == "RESTART_PROCESS") + return 13; + if (name == "SHUTDOWN_PROCESS") + return 14; + if (name == "ENABLE_HOST_SVC_CHECKS") + return 15; + if (name == "DISABLE_HOST_SVC_CHECKS") + return 16; + if (name == "SCHEDULE_HOST_SVC_CHECKS") + return 17; + if (name == "DELAY_HOST_SVC_NOTIFICATIONS") + return 19; + if (name == "DEL_ALL_HOST_COMMENTS") + return 20; + if (name == "DEL_ALL_SVC_COMMENTS") + return 21; + if (name == "ENABLE_SVC_NOTIFICATIONS") + return 22; + if (name == "DISABLE_SVC_NOTIFICATIONS") + return 23; + if (name == "ENABLE_HOST_NOTIFICATIONS") + return 24; + if (name == "DISABLE_HOST_NOTIFICATIONS") + return 25; + if (name == "ENABLE_ALL_NOTIFICATIONS_BEYOND_HOST") + return 26; + if (name == "DISABLE_ALL_NOTIFICATIONS_BEYOND_HOST") + return 27; + if (name == "ENABLE_HOST_SVC_NOTIFICATIONS") + return 28; + if (name == "DISABLE_HOST_SVC_NOTIFICATIONS") + return 29; + if (name == "PROCESS_SERVICE_CHECK_RESULT") + return 30; + if (name == "SAVE_STATE_INFORMATION") + return 31; + if (name == "READ_STATE_INFORMATION") + return 32; + if (name == "ACKNOWLEDGE_HOST_PROBLEM") + return 33; + if (name == "ACKNOWLEDGE_SVC_PROBLEM") + return 34; + if (name == "START_EXECUTING_SVC_CHECKS") + return 35; + if (name == "STOP_EXECUTING_SVC_CHECKS") + return 36; + if (name == "START_ACCEPTING_PASSIVE_SVC_CHECKS") + return 37; + if (name == "STOP_ACCEPTING_PASSIVE_SVC_CHECKS") + return 38; + if (name == "ENABLE_PASSIVE_SVC_CHECKS") + return 39; + if (name == "DISABLE_PASSIVE_SVC_CHECKS") + return 40; + if (name == "ENABLE_EVENT_HANDLERS") + return 41; + if (name == "DISABLE_EVENT_HANDLERS") + return 42; + if (name == "ENABLE_HOST_EVENT_HANDLER") + return 43; + if (name == "DISABLE_HOST_EVENT_HANDLER") + return 44; + if (name == "ENABLE_SVC_EVENT_HANDLER") + return 45; + if (name == "DISABLE_SVC_EVENT_HANDLER") + return 46; + if (name == "ENABLE_HOST_CHECK") + return 47; + if (name == "DISABLE_HOST_CHECK") + return 48; + if (name == "START_OBSESSING_OVER_SVC_CHECKS") + return 49; + if (name == "STOP_OBSESSING_OVER_SVC_CHECKS") + return 50; + if (name == "REMOVE_HOST_ACKNOWLEDGEMENT") + return 51; + if (name == "REMOVE_SVC_ACKNOWLEDGEMENT") + return 52; + if (name == "SCHEDULE_FORCED_HOST_SVC_CHECKS") + return 53; + if (name == "SCHEDULE_FORCED_SVC_CHECK") + return 54; + if (name == "SCHEDULE_HOST_DOWNTIME") + return 55; + if (name == "SCHEDULE_SVC_DOWNTIME") + return 56; + if (name == "ENABLE_HOST_FLAP_DETECTION") + return 57; + if (name == "DISABLE_HOST_FLAP_DETECTION") + return 58; + if (name == "ENABLE_SVC_FLAP_DETECTION") + return 59; + if (name == "DISABLE_SVC_FLAP_DETECTION") + return 60; + if (name == "ENABLE_FLAP_DETECTION") + return 61; + if (name == "DISABLE_FLAP_DETECTION") + return 62; + if (name == "ENABLE_HOSTGROUP_SVC_NOTIFICATIONS") + return 63; + if (name == "DISABLE_HOSTGROUP_SVC_NOTIFICATIONS") + return 64; + if (name == "ENABLE_HOSTGROUP_HOST_NOTIFICATIONS") + return 65; + if (name == "DISABLE_HOSTGROUP_HOST_NOTIFICATIONS") + return 66; + if (name == "ENABLE_HOSTGROUP_SVC_CHECKS") + return 67; + if (name == "DISABLE_HOSTGROUP_SVC_CHECKS") + return 68; + if (name == "CANCEL_HOST_DOWNTIME") + return 69; + if (name == "CANCEL_SVC_DOWNTIME") + return 70; + if (name == "CANCEL_ACTIVE_HOST_DOWNTIME") + return 71; + if (name == "CANCEL_PENDING_HOST_DOWNTIME") + return 72; + if (name == "CANCEL_ACTIVE_SVC_DOWNTIME") + return 73; + if (name == "CANCEL_PENDING_SVC_DOWNTIME") + return 74; + if (name == "CANCEL_ACTIVE_HOST_SVC_DOWNTIME") + return 75; + if (name == "CANCEL_PENDING_HOST_SVC_DOWNTIME") + return 76; + if (name == "FLUSH_PENDING_COMMANDS") + return 77; + if (name == "DEL_HOST_DOWNTIME") + return 78; + if (name == "DEL_SVC_DOWNTIME") + return 79; + if (name == "ENABLE_FAILURE_PREDICTION") + return 80; + if (name == "DISABLE_FAILURE_PREDICTION") + return 81; + if (name == "ENABLE_PERFORMANCE_DATA") + return 82; + if (name == "DISABLE_PERFORMANCE_DATA") + return 83; + if (name == "SCHEDULE_HOSTGROUP_HOST_DOWNTIME") + return 84; + if (name == "SCHEDULE_HOSTGROUP_SVC_DOWNTIME") + return 85; + if (name == "SCHEDULE_HOST_SVC_DOWNTIME") + return 86; + if (name == "PROCESS_HOST_CHECK_RESULT") + return 87; + if (name == "START_EXECUTING_HOST_CHECKS") + return 88; + if (name == "STOP_EXECUTING_HOST_CHECKS") + return 89; + if (name == "START_ACCEPTING_PASSIVE_HOST_CHECKS") + return 90; + if (name == "STOP_ACCEPTING_PASSIVE_HOST_CHECKS") + return 91; + if (name == "ENABLE_PASSIVE_HOST_CHECKS") + return 92; + if (name == "DISABLE_PASSIVE_HOST_CHECKS") + return 93; + if (name == "START_OBSESSING_OVER_HOST_CHECKS") + return 94; + if (name == "STOP_OBSESSING_OVER_HOST_CHECKS") + return 95; + if (name == "SCHEDULE_HOST_CHECK") + return 96; + if (name == "SCHEDULE_FORCED_HOST_CHECK") + return 98; + if (name == "START_OBSESSING_OVER_SVC") + return 99; + if (name == "STOP_OBSESSING_OVER_SVC") + return 100; + if (name == "START_OBSESSING_OVER_HOST") + return 101; + if (name == "STOP_OBSESSING_OVER_HOST") + return 102; + if (name == "ENABLE_HOSTGROUP_HOST_CHECKS") + return 103; + if (name == "DISABLE_HOSTGROUP_HOST_CHECKS") + return 104; + if (name == "ENABLE_HOSTGROUP_PASSIVE_SVC_CHECKS") + return 105; + if (name == "DISABLE_HOSTGROUP_PASSIVE_SVC_CHECKS") + return 106; + if (name == "ENABLE_HOSTGROUP_PASSIVE_HOST_CHECKS") + return 107; + if (name == "DISABLE_HOSTGROUP_PASSIVE_HOST_CHECKS") + return 108; + if (name == "ENABLE_SERVICEGROUP_SVC_NOTIFICATIONS") + return 109; + if (name == "DISABLE_SERVICEGROUP_SVC_NOTIFICATIONS") + return 110; + if (name == "ENABLE_SERVICEGROUP_HOST_NOTIFICATIONS") + return 111; + if (name == "DISABLE_SERVICEGROUP_HOST_NOTIFICATIONS") + return 112; + if (name == "ENABLE_SERVICEGROUP_SVC_CHECKS") + return 113; + if (name == "DISABLE_SERVICEGROUP_SVC_CHECKS") + return 114; + if (name == "ENABLE_SERVICEGROUP_HOST_CHECKS") + return 115; + if (name == "DISABLE_SERVICEGROUP_HOST_CHECKS") + return 116; + if (name == "ENABLE_SERVICEGROUP_PASSIVE_SVC_CHECKS") + return 117; + if (name == "DISABLE_SERVICEGROUP_PASSIVE_SVC_CHECKS") + return 118; + if (name == "ENABLE_SERVICEGROUP_PASSIVE_HOST_CHECKS") + return 119; + if (name == "DISABLE_SERVICEGROUP_PASSIVE_HOST_CHECKS") + return 120; + if (name == "SCHEDULE_SERVICEGROUP_HOST_DOWNTIME") + return 121; + if (name == "SCHEDULE_SERVICEGROUP_SVC_DOWNTIME") + return 122; + if (name == "CHANGE_GLOBAL_HOST_EVENT_HANDLER") + return 123; + if (name == "CHANGE_GLOBAL_SVC_EVENT_HANDLER") + return 124; + if (name == "CHANGE_HOST_EVENT_HANDLER") + return 125; + if (name == "CHANGE_SVC_EVENT_HANDLER") + return 126; + if (name == "CHANGE_HOST_CHECK_COMMAND") + return 127; + if (name == "CHANGE_SVC_CHECK_COMMAND") + return 128; + if (name == "CHANGE_NORMAL_HOST_CHECK_INTERVAL") + return 129; + if (name == "CHANGE_NORMAL_SVC_CHECK_INTERVAL") + return 130; + if (name == "CHANGE_RETRY_SVC_CHECK_INTERVAL") + return 131; + if (name == "CHANGE_MAX_HOST_CHECK_ATTEMPTS") + return 132; + if (name == "CHANGE_MAX_SVC_CHECK_ATTEMPTS") + return 133; + if (name == "SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME") + return 134; + if (name == "ENABLE_HOST_AND_CHILD_NOTIFICATIONS") + return 135; + if (name == "DISABLE_HOST_AND_CHILD_NOTIFICATIONS") + return 136; + if (name == "SCHEDULE_AND_PROPAGATE_HOST_DOWNTIME") + return 137; + if (name == "ENABLE_SERVICE_FRESHNESS_CHECKS") + return 138; + if (name == "DISABLE_SERVICE_FRESHNESS_CHECKS") + return 139; + if (name == "ENABLE_HOST_FRESHNESS_CHECKS") + return 140; + if (name == "DISABLE_HOST_FRESHNESS_CHECKS") + return 141; + if (name == "SET_HOST_NOTIFICATION_NUMBER") + return 142; + if (name == "SET_SVC_NOTIFICATION_NUMBER") + return 143; + if (name == "CHANGE_HOST_CHECK_TIMEPERIOD") + return 144; + if (name == "CHANGE_SVC_CHECK_TIMEPERIOD") + return 145; + if (name == "PROCESS_FILE") + return 146; + if (name == "CHANGE_CUSTOM_HOST_VAR") + return 147; + if (name == "CHANGE_CUSTOM_SVC_VAR") + return 148; + if (name == "CHANGE_CUSTOM_CONTACT_VAR") + return 149; + if (name == "ENABLE_CONTACT_HOST_NOTIFICATIONS") + return 150; + if (name == "DISABLE_CONTACT_HOST_NOTIFICATIONS") + return 151; + if (name == "ENABLE_CONTACT_SVC_NOTIFICATIONS") + return 152; + if (name == "DISABLE_CONTACT_SVC_NOTIFICATIONS") + return 153; + if (name == "ENABLE_CONTACTGROUP_HOST_NOTIFICATIONS") + return 154; + if (name == "DISABLE_CONTACTGROUP_HOST_NOTIFICATIONS") + return 155; + if (name == "ENABLE_CONTACTGROUP_SVC_NOTIFICATIONS") + return 156; + if (name == "DISABLE_CONTACTGROUP_SVC_NOTIFICATIONS") + return 157; + if (name == "CHANGE_RETRY_HOST_CHECK_INTERVAL") + return 158; + if (name == "SEND_CUSTOM_HOST_NOTIFICATION") + return 159; + if (name == "SEND_CUSTOM_SVC_NOTIFICATION") + return 160; + if (name == "CHANGE_HOST_NOTIFICATION_TIMEPERIOD") + return 161; + if (name == "CHANGE_SVC_NOTIFICATION_TIMEPERIOD") + return 162; + if (name == "CHANGE_CONTACT_HOST_NOTIFICATION_TIMEPERIOD") + return 163; + if (name == "CHANGE_CONTACT_SVC_NOTIFICATION_TIMEPERIOD") + return 164; + if (name == "CHANGE_HOST_MODATTR") + return 165; + if (name == "CHANGE_SVC_MODATTR") + return 166; + if (name == "CHANGE_CONTACT_MODATTR") + return 167; + if (name == "CHANGE_CONTACT_MODHATTR") + return 168; + if (name == "CHANGE_CONTACT_MODSATTR") + return 169; + if (name == "SYNC_STATE_INFORMATION") + return 170; + if (name == "DEL_DOWNTIME_BY_HOST_NAME") + return 171; + if (name == "DEL_DOWNTIME_BY_HOSTGROUP_NAME") + return 172; + if (name == "DEL_DOWNTIME_BY_START_TIME_COMMENT") + return 173; + if (name == "ACKNOWLEDGE_HOST_PROBLEM_EXPIRE") + return 174; + if (name == "ACKNOWLEDGE_SVC_PROBLEM_EXPIRE") + return 175; + if (name == "DISABLE_NOTIFICATIONS_EXPIRE_TIME") + return 176; + if (name == "CUSTOM_COMMAND") + return 999; + + return 0; +} diff --git a/lib/db_ido/dbevents.hpp b/lib/db_ido/dbevents.hpp new file mode 100644 index 0000000..858f3b3 --- /dev/null +++ b/lib/db_ido/dbevents.hpp @@ -0,0 +1,128 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBEVENTS_H +#define DBEVENTS_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" +#include "icinga/service.hpp" + +namespace icinga +{ + +enum LogEntryType +{ + LogEntryTypeRuntimeError = 1, + LogEntryTypeRuntimeWarning = 2, + LogEntryTypeVerificationError = 4, + LogEntryTypeVerificationWarning = 8, + LogEntryTypeConfigError = 16, + LogEntryTypeConfigWarning = 32, + LogEntryTypeProcessInfo = 64, + LogEntryTypeEventHandler = 128, + LogEntryTypeExternalCommand = 512, + LogEntryTypeHostUp = 1024, + LogEntryTypeHostDown = 2048, + LogEntryTypeHostUnreachable = 4096, + LogEntryTypeServiceOk = 8192, + LogEntryTypeServiceUnknown = 16384, + LogEntryTypeServiceWarning = 32768, + LogEntryTypeServiceCritical = 65536, + LogEntryTypePassiveCheck = 1231072, + LogEntryTypeInfoMessage = 262144, + LogEntryTypeHostNotification = 524288, + LogEntryTypeServiceNotification = 1048576 +}; + +/** + * IDO events + * + * @ingroup ido + */ +class DbEvents +{ +public: + static void StaticInitialize(); + + static void AddComments(const Checkable::Ptr& checkable); + + static void AddDowntimes(const Checkable::Ptr& checkable); + static void RemoveDowntimes(const Checkable::Ptr& checkable); + + static void AddLogHistory(const Checkable::Ptr& checkable, const String& buffer, LogEntryType type); + + /* Status */ + static void NextCheckUpdatedHandler(const Checkable::Ptr& checkable); + static void FlappingChangedHandler(const Checkable::Ptr& checkable); + static void LastNotificationChangedHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable); + + static void EnableActiveChecksChangedHandler(const Checkable::Ptr& checkable); + static void EnablePassiveChecksChangedHandler(const Checkable::Ptr& checkable); + static void EnableNotificationsChangedHandler(const Checkable::Ptr& checkable); + static void EnablePerfdataChangedHandler(const Checkable::Ptr& checkable); + static void EnableFlappingChangedHandler(const Checkable::Ptr& checkable); + + static void AddComment(const Comment::Ptr& comment); + static void RemoveComment(const Comment::Ptr& comment); + + static void AddDowntime(const Downtime::Ptr& downtime); + static void RemoveDowntime(const Downtime::Ptr& downtime); + static void TriggerDowntime(const Downtime::Ptr& downtime); + + static void AddAcknowledgement(const Checkable::Ptr& checkable, AcknowledgementType type); + static void RemoveAcknowledgement(const Checkable::Ptr& checkable); + static void AddAcknowledgementInternal(const Checkable::Ptr& checkable, AcknowledgementType type, bool add); + + static void ReachabilityChangedHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, std::set<Checkable::Ptr> children); + + /* comment, downtime, acknowledgement history */ + static void AddCommentHistory(const Comment::Ptr& comment); + static void AddDowntimeHistory(const Downtime::Ptr& downtime); + static void AddAcknowledgementHistory(const Checkable::Ptr& checkable, const String& author, const String& comment, + AcknowledgementType type, bool notify, double expiry); + + /* notification & contactnotification history */ + static void AddNotificationHistory(const Notification::Ptr& notification, const Checkable::Ptr& checkable, + const std::set<User::Ptr>& users, NotificationType type, const CheckResult::Ptr& cr, const String& author, + const String& text); + + /* statehistory */ + static void AddStateChangeHistory(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); + + /* logentries */ + static void AddCheckResultLogHistory(const Checkable::Ptr& checkable, const CheckResult::Ptr &cr); + static void AddTriggerDowntimeLogHistory(const Downtime::Ptr& downtime); + static void AddRemoveDowntimeLogHistory(const Downtime::Ptr& downtime); + static void AddNotificationSentLogHistory(const Notification::Ptr& notification, const Checkable::Ptr& checkable, + const User::Ptr& user, NotificationType notification_type, const CheckResult::Ptr& cr, const String& author, + const String& comment_text); + + static void AddFlappingChangedLogHistory(const Checkable::Ptr& checkable); + static void AddEnableFlappingChangedLogHistory(const Checkable::Ptr& checkable); + + /* other history */ + static void AddFlappingChangedHistory(const Checkable::Ptr& checkable); + static void AddEnableFlappingChangedHistory(const Checkable::Ptr& checkable); + static void AddCheckableCheckHistory(const Checkable::Ptr& checkable, const CheckResult::Ptr &cr); + static void AddEventHandlerHistory(const Checkable::Ptr& checkable); + static void AddExternalCommandHistory(double time, const String& command, const std::vector<String>& arguments); + +private: + DbEvents(); + + static void AddCommentInternal(std::vector<DbQuery>& queries, const Comment::Ptr& comment, bool historical); + static void RemoveCommentInternal(std::vector<DbQuery>& queries, const Comment::Ptr& comment); + static void AddDowntimeInternal(std::vector<DbQuery>& queries, const Downtime::Ptr& downtime, bool historical); + static void RemoveDowntimeInternal(std::vector<DbQuery>& queries, const Downtime::Ptr& downtime); + static void EnableChangedHandlerInternal(const Checkable::Ptr& checkable, const String& fieldName, bool enabled); + + static int GetHostState(const Host::Ptr& host); + static String GetHostStateString(const Host::Ptr& host); + static std::pair<unsigned long, unsigned long> ConvertTimestamp(double time); + static int MapNotificationReasonType(NotificationType type); + static int MapExternalCommandType(const String& name); +}; + +} + +#endif /* DBEVENTS_H */ diff --git a/lib/db_ido/dbobject.cpp b/lib/db_ido/dbobject.cpp new file mode 100644 index 0000000..406bf52 --- /dev/null +++ b/lib/db_ido/dbobject.cpp @@ -0,0 +1,430 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/customvarobject.hpp" +#include "icinga/service.hpp" +#include "icinga/compatutility.hpp" +#include "icinga/checkcommand.hpp" +#include "icinga/eventcommand.hpp" +#include "icinga/notificationcommand.hpp" +#include "remote/endpoint.hpp" +#include "base/configobject.hpp" +#include "base/configtype.hpp" +#include "base/json.hpp" +#include "base/serializer.hpp" +#include "base/json.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/utility.hpp" +#include "base/initialize.hpp" +#include "base/logger.hpp" + +using namespace icinga; + +boost::signals2::signal<void (const DbQuery&)> DbObject::OnQuery; +boost::signals2::signal<void (const std::vector<DbQuery>&)> DbObject::OnMultipleQueries; +boost::signals2::signal<void (const std::function<void (const DbObject::QueryCallbacks&)>&)> DbObject::OnMakeQueries; + +INITIALIZE_ONCE(&DbObject::StaticInitialize); + +DbObject::DbObject(intrusive_ptr<DbType> type, String name1, String name2) + : m_Name1(std::move(name1)), m_Name2(std::move(name2)), m_Type(std::move(type)), m_LastConfigUpdate(0), m_LastStatusUpdate(0) +{ } + +void DbObject::StaticInitialize() +{ + /* triggered in ProcessCheckResult(), requires UpdateNextCheck() to be called before */ + ConfigObject::OnStateChanged.connect([](const ConfigObject::Ptr& object) { StateChangedHandler(object); }); + CustomVarObject::OnVarsChanged.connect([](const CustomVarObject::Ptr& customVar, const Value&) { VarsChangedHandler(customVar); }); + + /* triggered on create, update and delete objects */ + ConfigObject::OnVersionChanged.connect([](const ConfigObject::Ptr& object, const Value&) { VersionChangedHandler(object); }); +} + +void DbObject::SetObject(const ConfigObject::Ptr& object) +{ + m_Object = object; +} + +ConfigObject::Ptr DbObject::GetObject() const +{ + return m_Object; +} + +String DbObject::GetName1() const +{ + return m_Name1; +} + +String DbObject::GetName2() const +{ + return m_Name2; +} + +DbType::Ptr DbObject::GetType() const +{ + return m_Type; +} + +String DbObject::CalculateConfigHash(const Dictionary::Ptr& configFields) const +{ + Dictionary::Ptr configFieldsDup = configFields->ShallowClone(); + + { + ObjectLock olock(configFieldsDup); + + for (const Dictionary::Pair& kv : configFieldsDup) { + if (kv.second.IsObjectType<ConfigObject>()) { + ConfigObject::Ptr obj = kv.second; + configFieldsDup->Set(kv.first, obj->GetName()); + } + } + } + + Array::Ptr data = new Array(); + data->Add(configFieldsDup); + + CustomVarObject::Ptr custom_var_object = dynamic_pointer_cast<CustomVarObject>(GetObject()); + + if (custom_var_object) + data->Add(custom_var_object->GetVars()); + + return HashValue(data); +} + +String DbObject::HashValue(const Value& value) +{ + Value temp; + + Type::Ptr type = value.GetReflectionType(); + + if (ConfigObject::TypeInstance->IsAssignableFrom(type)) + temp = Serialize(value, FAConfig); + else + temp = value; + + return SHA256(JsonEncode(temp)); +} + +void DbObject::SendConfigUpdateHeavy(const Dictionary::Ptr& configFields) +{ + /* update custom var config and status */ + SendVarsConfigUpdateHeavy(); + + /* config attributes */ + if (!configFields) + return; + + ASSERT(configFields->Contains("config_hash")); + + ConfigObject::Ptr object = GetObject(); + + DbQuery query; + query.Table = GetType()->GetTable() + "s"; + query.Type = DbQueryInsert | DbQueryUpdate; + query.Category = DbCatConfig; + query.Fields = configFields; + query.Fields->Set(GetType()->GetIDColumn(), object); + query.Fields->Set("instance_id", 0); /* DbConnection class fills in real ID */ + query.Fields->Set("config_type", 1); + query.WhereCriteria = new Dictionary({ + { GetType()->GetIDColumn(), object } + }); + query.Object = this; + query.ConfigUpdate = true; + OnQuery(query); + + m_LastConfigUpdate = Utility::GetTime(); + + OnConfigUpdateHeavy(); +} + +void DbObject::SendConfigUpdateLight() +{ + OnConfigUpdateLight(); +} + +void DbObject::SendStatusUpdate() +{ + /* status attributes */ + Dictionary::Ptr fields = GetStatusFields(); + + if (!fields) + return; + + DbQuery query; + query.Table = GetType()->GetTable() + "status"; + query.Type = DbQueryInsert | DbQueryUpdate; + query.Category = DbCatState; + query.Fields = fields; + query.Fields->Set(GetType()->GetIDColumn(), GetObject()); + + /* do not override endpoint_object_id for endpoints & zones */ + if (query.Table != "endpointstatus" && query.Table != "zonestatus") { + String node = IcingaApplication::GetInstance()->GetNodeName(); + + Endpoint::Ptr endpoint = Endpoint::GetByName(node); + if (endpoint) + query.Fields->Set("endpoint_object_id", endpoint); + } + + query.Fields->Set("instance_id", 0); /* DbConnection class fills in real ID */ + + query.Fields->Set("status_update_time", DbValue::FromTimestamp(Utility::GetTime())); + query.WhereCriteria = new Dictionary({ + { GetType()->GetIDColumn(), GetObject() } + }); + query.Object = this; + query.StatusUpdate = true; + OnQuery(query); + + m_LastStatusUpdate = Utility::GetTime(); + + OnStatusUpdate(); +} + +void DbObject::SendVarsConfigUpdateHeavy() +{ + ConfigObject::Ptr obj = GetObject(); + + CustomVarObject::Ptr custom_var_object = dynamic_pointer_cast<CustomVarObject>(obj); + + if (!custom_var_object) + return; + + std::vector<DbQuery> queries; + + DbQuery query1; + query1.Table = "customvariables"; + query1.Type = DbQueryDelete; + query1.Category = DbCatConfig; + query1.WhereCriteria = new Dictionary({ + { "object_id", obj } + }); + queries.emplace_back(std::move(query1)); + + DbQuery query2; + query2.Table = "customvariablestatus"; + query2.Type = DbQueryDelete; + query2.Category = DbCatConfig; + query2.WhereCriteria = new Dictionary({ + { "object_id", obj } + }); + queries.emplace_back(std::move(query2)); + + Dictionary::Ptr vars = custom_var_object->GetVars(); + + if (vars) { + ObjectLock olock (vars); + + for (const Dictionary::Pair& kv : vars) { + if (kv.first.IsEmpty()) + continue; + + String value; + int is_json = 0; + + if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>()) { + value = JsonEncode(kv.second); + is_json = 1; + } else + value = kv.second; + + DbQuery query3; + query3.Table = "customvariables"; + query3.Type = DbQueryInsert; + query3.Category = DbCatConfig; + query3.Fields = new Dictionary({ + { "varname", kv.first }, + { "varvalue", value }, + { "is_json", is_json }, + { "config_type", 1 }, + { "object_id", obj }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query3)); + + DbQuery query4; + query4.Table = "customvariablestatus"; + query4.Type = DbQueryInsert; + query4.Category = DbCatState; + + query4.Fields = new Dictionary({ + { "varname", kv.first }, + { "varvalue", value }, + { "is_json", is_json }, + { "status_update_time", DbValue::FromTimestamp(Utility::GetTime()) }, + { "object_id", obj }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + queries.emplace_back(std::move(query4)); + } + } + + OnMultipleQueries(queries); +} + +void DbObject::SendVarsStatusUpdate() +{ + ConfigObject::Ptr obj = GetObject(); + + CustomVarObject::Ptr custom_var_object = dynamic_pointer_cast<CustomVarObject>(obj); + + if (!custom_var_object) + return; + + Dictionary::Ptr vars = custom_var_object->GetVars(); + + if (vars) { + std::vector<DbQuery> queries; + ObjectLock olock (vars); + + for (const Dictionary::Pair& kv : vars) { + if (kv.first.IsEmpty()) + continue; + + String value; + int is_json = 0; + + if (kv.second.IsObjectType<Array>() || kv.second.IsObjectType<Dictionary>()) { + value = JsonEncode(kv.second); + is_json = 1; + } else + value = kv.second; + + DbQuery query; + query.Table = "customvariablestatus"; + query.Type = DbQueryInsert | DbQueryUpdate; + query.Category = DbCatState; + + query.Fields = new Dictionary({ + { "varname", kv.first }, + { "varvalue", value }, + { "is_json", is_json }, + { "status_update_time", DbValue::FromTimestamp(Utility::GetTime()) }, + { "object_id", obj }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + query.WhereCriteria = new Dictionary({ + { "object_id", obj }, + { "varname", kv.first } + }); + + queries.emplace_back(std::move(query)); + } + + OnMultipleQueries(queries); + } +} + +double DbObject::GetLastConfigUpdate() const +{ + return m_LastConfigUpdate; +} + +double DbObject::GetLastStatusUpdate() const +{ + return m_LastStatusUpdate; +} + +void DbObject::OnConfigUpdateHeavy() +{ + /* Default handler does nothing. */ +} + +void DbObject::OnConfigUpdateLight() +{ + /* Default handler does nothing. */ +} + +void DbObject::OnStatusUpdate() +{ + /* Default handler does nothing. */ +} + +DbObject::Ptr DbObject::GetOrCreateByObject(const ConfigObject::Ptr& object) +{ + std::unique_lock<std::mutex> lock(GetStaticMutex()); + + DbObject::Ptr dbobj = object->GetExtension("DbObject"); + + if (dbobj) + return dbobj; + + DbType::Ptr dbtype = DbType::GetByName(object->GetReflectionType()->GetName()); + + if (!dbtype) + return nullptr; + + Service::Ptr service; + String name1, name2; + + service = dynamic_pointer_cast<Service>(object); + + if (service) { + Host::Ptr host = service->GetHost(); + + name1 = service->GetHost()->GetName(); + name2 = service->GetShortName(); + } else { + if (object->GetReflectionType() == CheckCommand::TypeInstance || + object->GetReflectionType() == EventCommand::TypeInstance || + object->GetReflectionType() == NotificationCommand::TypeInstance) { + Command::Ptr command = dynamic_pointer_cast<Command>(object); + name1 = CompatUtility::GetCommandName(command); + } + else + name1 = object->GetName(); + } + + dbobj = dbtype->GetOrCreateObjectByName(name1, name2); + + dbobj->SetObject(object); + object->SetExtension("DbObject", dbobj); + + return dbobj; +} + +void DbObject::StateChangedHandler(const ConfigObject::Ptr& object) +{ + DbObject::Ptr dbobj = GetOrCreateByObject(object); + + if (!dbobj) + return; + + dbobj->SendStatusUpdate(); +} + +void DbObject::VarsChangedHandler(const CustomVarObject::Ptr& object) +{ + DbObject::Ptr dbobj = GetOrCreateByObject(object); + + if (!dbobj) + return; + + dbobj->SendVarsStatusUpdate(); +} + +void DbObject::VersionChangedHandler(const ConfigObject::Ptr& object) +{ + DbObject::Ptr dbobj = DbObject::GetOrCreateByObject(object); + + if (dbobj) { + Dictionary::Ptr configFields = dbobj->GetConfigFields(); + String configHash = dbobj->CalculateConfigHash(configFields); + configFields->Set("config_hash", configHash); + + dbobj->SendConfigUpdateHeavy(configFields); + dbobj->SendStatusUpdate(); + } +} + +std::mutex& DbObject::GetStaticMutex() +{ + static std::mutex mutex; + return mutex; +} diff --git a/lib/db_ido/dbobject.hpp b/lib/db_ido/dbobject.hpp new file mode 100644 index 0000000..399b77d --- /dev/null +++ b/lib/db_ido/dbobject.hpp @@ -0,0 +1,112 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBOBJECT_H +#define DBOBJECT_H + +#include "db_ido/i2-db_ido.hpp" +#include "db_ido/dbreference.hpp" +#include "db_ido/dbquery.hpp" +#include "db_ido/dbtype.hpp" +#include "icinga/customvarobject.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +enum DbObjectUpdateType +{ + DbObjectCreated, + DbObjectRemoved +}; + +enum DbObjectType +{ + DbObjectTypeHost = 1, + DbObjectTypeService = 2, + DbObjectTypeHostGroup = 3, + DbObjectTypeServiceGroup = 4, + DbObjectTypeHostEscalation = 5, + DbObjectTypeServiceEscalation = 6, + DbObjectTypeHostDependency = 7, + DbObjectTypeServiceDependency = 8, + DbObjectTypeTimePeriod = 9, + DbObjectTypeContact = 10, + DbObjectTypeContactGroup = 11, + DbObjectTypeCommand = 12, + DbObjectTypeEndpoint = 13, + DbObjectTypeZone = 14, +}; + +/** + * A database object. + * + * @ingroup ido + */ +class DbObject : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(DbObject); + + static void StaticInitialize(); + + void SetObject(const ConfigObject::Ptr& object); + ConfigObject::Ptr GetObject() const; + + String GetName1() const; + String GetName2() const; + intrusive_ptr<DbType> GetType() const; + + virtual Dictionary::Ptr GetConfigFields() const = 0; + virtual Dictionary::Ptr GetStatusFields() const = 0; + + static DbObject::Ptr GetOrCreateByObject(const ConfigObject::Ptr& object); + + struct QueryCallbacks { + std::function<void(const DbQuery&)> Query; + std::function<void(const std::vector<DbQuery>&)> MultipleQueries; + }; + + static boost::signals2::signal<void (const DbQuery&)> OnQuery; + static boost::signals2::signal<void (const std::vector<DbQuery>&)> OnMultipleQueries; + static boost::signals2::signal<void (const std::function<void (const QueryCallbacks&)>&)> OnMakeQueries; + + void SendConfigUpdateHeavy(const Dictionary::Ptr& configFields); + void SendConfigUpdateLight(); + void SendStatusUpdate(); + void SendVarsConfigUpdateHeavy(); + void SendVarsStatusUpdate(); + + double GetLastConfigUpdate() const; + double GetLastStatusUpdate() const; + + virtual String CalculateConfigHash(const Dictionary::Ptr& configFields) const; + +protected: + DbObject(intrusive_ptr<DbType> type, String name1, String name2); + + virtual void OnConfigUpdateHeavy(); + virtual void OnConfigUpdateLight(); + virtual void OnStatusUpdate(); + + static String HashValue(const Value& value); + +private: + String m_Name1; + String m_Name2; + intrusive_ptr<DbType> m_Type; + ConfigObject::Ptr m_Object; + double m_LastConfigUpdate; + double m_LastStatusUpdate; + + static void StateChangedHandler(const ConfigObject::Ptr& object); + static void VarsChangedHandler(const CustomVarObject::Ptr& object); + static void VersionChangedHandler(const ConfigObject::Ptr& object); + + static std::mutex& GetStaticMutex(); + + friend class DbType; +}; + +} + +#endif /* DBOBJECT_H */ diff --git a/lib/db_ido/dbquery.cpp b/lib/db_ido/dbquery.cpp new file mode 100644 index 0000000..1de2928 --- /dev/null +++ b/lib/db_ido/dbquery.cpp @@ -0,0 +1,52 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbquery.hpp" +#include "base/initialize.hpp" +#include "base/scriptglobal.hpp" + +using namespace icinga; + +INITIALIZE_ONCE(&DbQuery::StaticInitialize); + +std::map<String, int> DbQuery::m_CategoryFilterMap; + +void DbQuery::StaticInitialize() +{ + ScriptGlobal::Set("Icinga.DbCatConfig", DbCatConfig); + ScriptGlobal::Set("Icinga.DbCatState", DbCatState); + ScriptGlobal::Set("Icinga.DbCatAcknowledgement", DbCatAcknowledgement); + ScriptGlobal::Set("Icinga.DbCatComment", DbCatComment); + ScriptGlobal::Set("Icinga.DbCatDowntime", DbCatDowntime); + ScriptGlobal::Set("Icinga.DbCatEventHandler", DbCatEventHandler); + ScriptGlobal::Set("Icinga.DbCatExternalCommand", DbCatExternalCommand); + ScriptGlobal::Set("Icinga.DbCatFlapping", DbCatFlapping); + ScriptGlobal::Set("Icinga.DbCatCheck", DbCatCheck); + ScriptGlobal::Set("Icinga.DbCatLog", DbCatLog); + ScriptGlobal::Set("Icinga.DbCatNotification", DbCatNotification); + ScriptGlobal::Set("Icinga.DbCatProgramStatus", DbCatProgramStatus); + ScriptGlobal::Set("Icinga.DbCatRetention", DbCatRetention); + ScriptGlobal::Set("Icinga.DbCatStateHistory", DbCatStateHistory); + + ScriptGlobal::Set("Icinga.DbCatEverything", DbCatEverything); + + m_CategoryFilterMap["DbCatConfig"] = DbCatConfig; + m_CategoryFilterMap["DbCatState"] = DbCatState; + m_CategoryFilterMap["DbCatAcknowledgement"] = DbCatAcknowledgement; + m_CategoryFilterMap["DbCatComment"] = DbCatComment; + m_CategoryFilterMap["DbCatDowntime"] = DbCatDowntime; + m_CategoryFilterMap["DbCatEventHandler"] = DbCatEventHandler; + m_CategoryFilterMap["DbCatExternalCommand"] = DbCatExternalCommand; + m_CategoryFilterMap["DbCatFlapping"] = DbCatFlapping; + m_CategoryFilterMap["DbCatCheck"] = DbCatCheck; + m_CategoryFilterMap["DbCatLog"] = DbCatLog; + m_CategoryFilterMap["DbCatNotification"] = DbCatNotification; + m_CategoryFilterMap["DbCatProgramStatus"] = DbCatProgramStatus; + m_CategoryFilterMap["DbCatRetention"] = DbCatRetention; + m_CategoryFilterMap["DbCatStateHistory"] = DbCatStateHistory; + m_CategoryFilterMap["DbCatEverything"] = DbCatEverything; +} + +const std::map<String, int>& DbQuery::GetCategoryFilterMap() +{ + return m_CategoryFilterMap; +} diff --git a/lib/db_ido/dbquery.hpp b/lib/db_ido/dbquery.hpp new file mode 100644 index 0000000..fecb2e3 --- /dev/null +++ b/lib/db_ido/dbquery.hpp @@ -0,0 +1,72 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBQUERY_H +#define DBQUERY_H + +#include "db_ido/i2-db_ido.hpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/customvarobject.hpp" +#include "base/dictionary.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +enum DbQueryType +{ + DbQueryInsert = 1, + DbQueryUpdate = 2, + DbQueryDelete = 4, + DbQueryNewTransaction = 8 +}; + +enum DbQueryCategory +{ + DbCatInvalid = 0, //-1 is required for DbCatEverything + DbCatEverything = ~0, + + DbCatConfig = 1, + DbCatState = 2, + DbCatAcknowledgement = 4, + DbCatComment = 8, + DbCatDowntime = 16, + DbCatEventHandler = 32, + DbCatExternalCommand = 64, + DbCatFlapping = 128, + DbCatCheck = 256, + DbCatLog = 512, + DbCatNotification = 1024, + DbCatProgramStatus = 2048, + DbCatRetention = 4096, + DbCatStateHistory = 8192 +}; + +class DbObject; + +struct DbQuery +{ + int Type{0}; + DbQueryCategory Category{DbCatInvalid}; + String Table; + String IdColumn; + Dictionary::Ptr Fields; + Dictionary::Ptr WhereCriteria; + intrusive_ptr<DbObject> Object; + DbValue::Ptr NotificationInsertID; + bool ConfigUpdate{false}; + bool StatusUpdate{false}; + WorkQueuePriority Priority{PriorityNormal}; + + static void StaticInitialize(); + + static const std::map<String, int>& GetCategoryFilterMap(); + +private: + static std::map<String, int> m_CategoryFilterMap; +}; + +} + +#endif /* DBQUERY_H */ + +#include "db_ido/dbobject.hpp" diff --git a/lib/db_ido/dbreference.cpp b/lib/db_ido/dbreference.cpp new file mode 100644 index 0000000..e8f13c0 --- /dev/null +++ b/lib/db_ido/dbreference.cpp @@ -0,0 +1,19 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "dbreference.hpp" + +using namespace icinga; + +DbReference::DbReference(long id) + : m_Id(id) +{ } + +bool DbReference::IsValid() const +{ + return (m_Id != -1); +} + +DbReference::operator long() const +{ + return m_Id; +} diff --git a/lib/db_ido/dbreference.hpp b/lib/db_ido/dbreference.hpp new file mode 100644 index 0000000..70edf9a --- /dev/null +++ b/lib/db_ido/dbreference.hpp @@ -0,0 +1,30 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBREFERENCE_H +#define DBREFERENCE_H + +#include "db_ido/i2-db_ido.hpp" + +namespace icinga +{ + +/** + * A database reference. + * + * @ingroup ido + */ +struct DbReference +{ +public: + DbReference() = default; + DbReference(long id); + + bool IsValid() const; + operator long() const; +private: + long m_Id{-1}; +}; + +} + +#endif /* DBREFERENCE_H */ diff --git a/lib/db_ido/dbtype.cpp b/lib/db_ido/dbtype.cpp new file mode 100644 index 0000000..bc45dcb --- /dev/null +++ b/lib/db_ido/dbtype.cpp @@ -0,0 +1,141 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbtype.hpp" +#include "db_ido/dbconnection.hpp" +#include "base/objectlock.hpp" +#include "base/debug.hpp" +#include <boost/thread/once.hpp> + +using namespace icinga; + +DbType::DbType(String name, String table, long tid, String idcolumn, DbType::ObjectFactory factory) + : m_Name(std::move(name)), m_Table(std::move(table)), m_TypeID(tid), m_IDColumn(std::move(idcolumn)), m_ObjectFactory(std::move(factory)) +{ } + +String DbType::GetName() const +{ + return m_Name; +} + +String DbType::GetTable() const +{ + return m_Table; +} + +long DbType::GetTypeID() const +{ + return m_TypeID; +} + +String DbType::GetIDColumn() const +{ + return m_IDColumn; +} + +void DbType::RegisterType(const DbType::Ptr& type) +{ + std::unique_lock<std::mutex> lock(GetStaticMutex()); + GetTypes()[type->GetName()] = type; +} + +DbType::Ptr DbType::GetByName(const String& name) +{ + String typeName; + + if (name == "CheckCommand" || name == "NotificationCommand" || name == "EventCommand") + typeName = "Command"; + else + typeName = name; + + std::unique_lock<std::mutex> lock(GetStaticMutex()); + auto it = GetTypes().find(typeName); + + if (it == GetTypes().end()) + return nullptr; + + return it->second; +} + +DbType::Ptr DbType::GetByID(long tid) +{ + std::unique_lock<std::mutex> lock(GetStaticMutex()); + + for (const TypeMap::value_type& kv : GetTypes()) { + if (kv.second->GetTypeID() == tid) + return kv.second; + } + + return nullptr; +} + +DbObject::Ptr DbType::GetOrCreateObjectByName(const String& name1, const String& name2) +{ + ObjectLock olock(this); + + auto it = m_Objects.find(std::make_pair(name1, name2)); + + if (it != m_Objects.end()) + return it->second; + + DbObject::Ptr dbobj = m_ObjectFactory(this, name1, name2); + m_Objects[std::make_pair(name1, name2)] = dbobj; + + String objName = name1; + + if (!name2.IsEmpty()) + objName += "!" + name2; + + String objType = m_Name; + + if (m_TypeID == DbObjectTypeCommand) { + if (objName.SubStr(0, 6) == "check_") { + objType = "CheckCommand"; + objName = objName.SubStr(6); + } else if (objName.SubStr(0, 13) == "notification_") { + objType = "NotificationCommand"; + objName = objName.SubStr(13); + } else if (objName.SubStr(0, 6) == "event_") { + objType = "EventCommand"; + objName = objName.SubStr(6); + } + } + + dbobj->SetObject(ConfigObject::GetObject(objType, objName)); + + return dbobj; +} + +std::mutex& DbType::GetStaticMutex() +{ + static std::mutex mutex; + return mutex; +} + +/** + * Caller must hold static mutex. + */ +DbType::TypeMap& DbType::GetTypes() +{ + static DbType::TypeMap tm; + return tm; +} + +std::set<DbType::Ptr> DbType::GetAllTypes() +{ + std::set<DbType::Ptr> result; + + { + std::unique_lock<std::mutex> lock(GetStaticMutex()); + for (const auto& kv : GetTypes()) { + result.insert(kv.second); + } + } + + return result; +} + +DbTypeRegistry *DbTypeRegistry::GetInstance() +{ + return Singleton<DbTypeRegistry>::GetInstance(); +} + diff --git a/lib/db_ido/dbtype.hpp b/lib/db_ido/dbtype.hpp new file mode 100644 index 0000000..c8ebc45 --- /dev/null +++ b/lib/db_ido/dbtype.hpp @@ -0,0 +1,90 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBTYPE_H +#define DBTYPE_H + +#include "db_ido/i2-db_ido.hpp" +#include "base/object.hpp" +#include "base/registry.hpp" +#include "base/singleton.hpp" +#include <set> + +namespace icinga +{ + +class DbObject; + +/** + * A database object type. + * + * @ingroup ido + */ +class DbType final : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(DbType); + + typedef std::function<intrusive_ptr<DbObject> (const intrusive_ptr<DbType>&, const String&, const String&)> ObjectFactory; + typedef std::map<String, DbType::Ptr> TypeMap; + typedef std::map<std::pair<String, String>, intrusive_ptr<DbObject> > ObjectMap; + + DbType(String name, String table, long tid, String idcolumn, ObjectFactory factory); + + String GetName() const; + String GetTable() const; + long GetTypeID() const; + String GetIDColumn() const; + + static void RegisterType(const DbType::Ptr& type); + + static DbType::Ptr GetByName(const String& name); + static DbType::Ptr GetByID(long tid); + + intrusive_ptr<DbObject> GetOrCreateObjectByName(const String& name1, const String& name2); + + static std::set<DbType::Ptr> GetAllTypes(); + +private: + String m_Name; + String m_Table; + long m_TypeID; + String m_IDColumn; + ObjectFactory m_ObjectFactory; + + static std::mutex& GetStaticMutex(); + static TypeMap& GetTypes(); + + ObjectMap m_Objects; +}; + +/** + * A registry for DbType objects. + * + * @ingroup ido + */ +class DbTypeRegistry : public Registry<DbTypeRegistry, DbType::Ptr> +{ +public: + static DbTypeRegistry *GetInstance(); +}; + +/** + * Factory function for DbObject-based classes. + * + * @ingroup ido + */ +template<typename T> +intrusive_ptr<T> DbObjectFactory(const DbType::Ptr& type, const String& name1, const String& name2) +{ + return new T(type, name1, name2); +} + +#define REGISTER_DBTYPE(name, table, tid, idcolumn, type) \ + INITIALIZE_ONCE([]() { \ + DbType::Ptr dbtype = new DbType(#name, table, tid, idcolumn, DbObjectFactory<type>); \ + DbType::RegisterType(dbtype); \ + }) + +} + +#endif /* DBTYPE_H */ diff --git a/lib/db_ido/dbvalue.cpp b/lib/db_ido/dbvalue.cpp new file mode 100644 index 0000000..e1e3e6c --- /dev/null +++ b/lib/db_ido/dbvalue.cpp @@ -0,0 +1,69 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/dbvalue.hpp" + +using namespace icinga; + +DbValue::DbValue(DbValueType type, Value value) + : m_Type(type), m_Value(std::move(value)) +{ } + +Value DbValue::FromTimestamp(const Value& ts) +{ + if (ts.IsEmpty() || ts == 0) + return Empty; + + return new DbValue(DbValueTimestamp, ts); +} + +Value DbValue::FromValue(const Value& value) +{ + return value; +} + +Value DbValue::FromObjectInsertID(const Value& value) +{ + return new DbValue(DbValueObjectInsertID, value); +} + +bool DbValue::IsTimestamp(const Value& value) +{ + if (!value.IsObjectType<DbValue>()) + return false; + + DbValue::Ptr dbv = value; + return dbv->GetType() == DbValueTimestamp; +} + +bool DbValue::IsObjectInsertID(const Value& value) +{ + if (!value.IsObjectType<DbValue>()) + return false; + + DbValue::Ptr dbv = value; + return dbv->GetType() == DbValueObjectInsertID; +} + +Value DbValue::ExtractValue(const Value& value) +{ + if (!value.IsObjectType<DbValue>()) + return value; + + DbValue::Ptr dbv = value; + return dbv->GetValue(); +} + +DbValueType DbValue::GetType() const +{ + return m_Type; +} + +Value DbValue::GetValue() const +{ + return m_Value; +} + +void DbValue::SetValue(const Value& value) +{ + m_Value = value; +} diff --git a/lib/db_ido/dbvalue.hpp b/lib/db_ido/dbvalue.hpp new file mode 100644 index 0000000..cb59e3a --- /dev/null +++ b/lib/db_ido/dbvalue.hpp @@ -0,0 +1,52 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef DBVALUE_H +#define DBVALUE_H + +#include "db_ido/i2-db_ido.hpp" +#include "base/object.hpp" +#include "base/value.hpp" + +namespace icinga +{ + +enum DbValueType +{ + DbValueTimestamp, + DbValueObjectInsertID +}; + +/** + * A database value. + * + * @ingroup ido + */ +struct DbValue final : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(DbValue); + + DbValue(DbValueType type, Value value); + + static Value FromTimestamp(const Value& ts); + static Value FromValue(const Value& value); + static Value FromObjectInsertID(const Value& value); + + static bool IsTimestamp(const Value& value); + static bool IsObjectInsertID(const Value& value); + + static Value ExtractValue(const Value& value); + + DbValueType GetType() const; + + Value GetValue() const; + void SetValue(const Value& value); + +private: + DbValueType m_Type; + Value m_Value; +}; + +} + +#endif /* DBVALUE_H */ diff --git a/lib/db_ido/endpointdbobject.cpp b/lib/db_ido/endpointdbobject.cpp new file mode 100644 index 0000000..ea16dd7 --- /dev/null +++ b/lib/db_ido/endpointdbobject.cpp @@ -0,0 +1,91 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/endpointdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/icingaapplication.hpp" +#include "base/objectlock.hpp" +#include "base/initialize.hpp" +#include "base/configtype.hpp" +#include "base/utility.hpp" +#include "base/convert.hpp" +#include "base/logger.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(Endpoint, "endpoint", DbObjectTypeEndpoint, "endpoint_object_id", EndpointDbObject); + +INITIALIZE_ONCE(&EndpointDbObject::StaticInitialize); + +void EndpointDbObject::StaticInitialize() +{ + Endpoint::OnConnected.connect([](const Endpoint::Ptr& endpoint, const JsonRpcConnection::Ptr&) { EndpointDbObject::UpdateConnectedStatus(endpoint); }); + Endpoint::OnDisconnected.connect([](const Endpoint::Ptr& endpoint, const JsonRpcConnection::Ptr&) { EndpointDbObject::UpdateConnectedStatus(endpoint); }); +} + +EndpointDbObject::EndpointDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr EndpointDbObject::GetConfigFields() const +{ + Endpoint::Ptr endpoint = static_pointer_cast<Endpoint>(GetObject()); + + return new Dictionary({ + { "identity", endpoint->GetName() }, + { "node", IcingaApplication::GetInstance()->GetNodeName() }, + { "zone_object_id", endpoint->GetZone() } + }); +} + +Dictionary::Ptr EndpointDbObject::GetStatusFields() const +{ + Endpoint::Ptr endpoint = static_pointer_cast<Endpoint>(GetObject()); + + + Log(LogDebug, "EndpointDbObject") + << "update status for endpoint '" << endpoint->GetName() << "'"; + + return new Dictionary({ + { "identity", endpoint->GetName() }, + { "node", IcingaApplication::GetInstance()->GetNodeName() }, + { "zone_object_id", endpoint->GetZone() }, + { "is_connected", EndpointIsConnected(endpoint) } + }); +} + +void EndpointDbObject::UpdateConnectedStatus(const Endpoint::Ptr& endpoint) +{ + bool connected = EndpointIsConnected(endpoint); + + Log(LogDebug, "EndpointDbObject") + << "update is_connected=" << connected << " for endpoint '" << endpoint->GetName() << "'"; + + DbQuery query1; + query1.Table = "endpointstatus"; + query1.Type = DbQueryUpdate; + query1.Category = DbCatState; + + query1.Fields = new Dictionary({ + { "is_connected", (connected ? 1 : 0) }, + { "status_update_time", DbValue::FromTimestamp(Utility::GetTime()) } + }); + + query1.WhereCriteria = new Dictionary({ + { "endpoint_object_id", endpoint }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + + OnQuery(query1); +} + +int EndpointDbObject::EndpointIsConnected(const Endpoint::Ptr& endpoint) +{ + unsigned int is_connected = endpoint->GetConnected() ? 1 : 0; + + /* if identity is equal to node, fake is_connected */ + if (endpoint->GetName() == IcingaApplication::GetInstance()->GetNodeName()) + is_connected = 1; + + return is_connected; +} diff --git a/lib/db_ido/endpointdbobject.hpp b/lib/db_ido/endpointdbobject.hpp new file mode 100644 index 0000000..e4fba36 --- /dev/null +++ b/lib/db_ido/endpointdbobject.hpp @@ -0,0 +1,37 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef ENDPOINTDBOBJECT_H +#define ENDPOINTDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" +#include "remote/endpoint.hpp" + +namespace icinga +{ + +/** + * A Command database object. + * + * @ingroup ido + */ +class EndpointDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(EndpointDbObject); + + EndpointDbObject(const intrusive_ptr<DbType>& type, const String& name1, const String& name2); + + static void StaticInitialize(); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; + +private: + static void UpdateConnectedStatus(const Endpoint::Ptr& endpoint); + static int EndpointIsConnected(const Endpoint::Ptr& endpoint); +}; + +} + +#endif /* ENDPOINTDBOBJECT_H */ diff --git a/lib/db_ido/hostdbobject.cpp b/lib/db_ido/hostdbobject.cpp new file mode 100644 index 0000000..60d1a99 --- /dev/null +++ b/lib/db_ido/hostdbobject.cpp @@ -0,0 +1,423 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/hostdbobject.hpp" +#include "db_ido/hostgroupdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "db_ido/dbevents.hpp" +#include "icinga/host.hpp" +#include "icinga/service.hpp" +#include "icinga/notification.hpp" +#include "icinga/dependency.hpp" +#include "icinga/checkcommand.hpp" +#include "icinga/eventcommand.hpp" +#include "icinga/compatutility.hpp" +#include "icinga/pluginutility.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/logger.hpp" +#include "base/json.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(Host, "host", DbObjectTypeHost, "host_object_id", HostDbObject); + +HostDbObject::HostDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr HostDbObject::GetConfigFields() const +{ + Dictionary::Ptr fields = new Dictionary(); + Host::Ptr host = static_pointer_cast<Host>(GetObject()); + + /* Compatibility fallback. */ + String displayName = host->GetDisplayName(); + + unsigned long notificationStateFilter = CompatUtility::GetCheckableNotificationTypeFilter(host); + unsigned long notificationTypeFilter = CompatUtility::GetCheckableNotificationTypeFilter(host); + + return new Dictionary({ + { "alias", !displayName.IsEmpty() ? displayName : host->GetName() }, + { "display_name", displayName }, + { "address", host->GetAddress() }, + { "address6", host->GetAddress6() }, + { "check_command_object_id", host->GetCheckCommand() }, + { "eventhandler_command_object_id", host->GetEventCommand() }, + { "check_timeperiod_object_id", host->GetCheckPeriod() }, + { "check_interval", host->GetCheckInterval() / 60.0 }, + { "retry_interval", host->GetRetryInterval() / 60.0 }, + { "max_check_attempts", host->GetMaxCheckAttempts() }, + { "flap_detection_enabled", host->GetEnableFlapping() }, + { "low_flap_threshold", host->GetFlappingThresholdLow() }, + { "high_flap_threshold", host->GetFlappingThresholdLow() }, + { "process_performance_data", host->GetEnablePerfdata() }, + { "freshness_checks_enabled", 1 }, + { "freshness_threshold", Convert::ToLong(host->GetCheckInterval()) }, + { "event_handler_enabled", host->GetEnableEventHandler() }, + { "passive_checks_enabled", host->GetEnablePassiveChecks() }, + { "active_checks_enabled", host->GetEnableActiveChecks() }, + { "notifications_enabled", host->GetEnableNotifications() }, + { "notes", host->GetNotes() }, + { "notes_url", host->GetNotesUrl() }, + { "action_url", host->GetActionUrl() }, + { "icon_image", host->GetIconImage() }, + { "icon_image_alt", host->GetIconImageAlt() }, + { "notification_interval", CompatUtility::GetCheckableNotificationNotificationInterval(host) }, + { "notify_on_down", (notificationStateFilter & (ServiceWarning | ServiceCritical)) ? 1 : 0 }, + { "notify_on_unreachable", 1 }, /* We don't have this filter and state, and as such we don't filter such notifications. */ + { "notify_on_recovery", (notificationTypeFilter & NotificationRecovery) ? 1 : 0 }, + { "notify_on_flapping", (notificationTypeFilter & (NotificationFlappingStart | NotificationFlappingEnd)) ? 1 : 0 }, + { "notify_on_downtime", (notificationTypeFilter & (NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved)) ? 1 : 0 } + }); +} + +Dictionary::Ptr HostDbObject::GetStatusFields() const +{ + Dictionary::Ptr fields = new Dictionary(); + Host::Ptr host = static_pointer_cast<Host>(GetObject()); + + CheckResult::Ptr cr = host->GetLastCheckResult(); + + if (cr) { + fields->Set("output", CompatUtility::GetCheckResultOutput(cr)); + fields->Set("long_output", CompatUtility::GetCheckResultLongOutput(cr)); + fields->Set("perfdata", PluginUtility::FormatPerfdata(cr->GetPerformanceData())); + fields->Set("check_source", cr->GetCheckSource()); + fields->Set("latency", cr->CalculateLatency()); + fields->Set("execution_time", cr->CalculateExecutionTime()); + } + + int currentState = host->GetState(); + + if (currentState != HostUp && !host->GetLastReachable()) + currentState = 2; /* hardcoded compat state */ + + fields->Set("current_state", currentState); + fields->Set("has_been_checked", host->HasBeenChecked()); + fields->Set("should_be_scheduled", host->GetEnableActiveChecks()); + fields->Set("current_check_attempt", host->GetCheckAttempt()); + fields->Set("max_check_attempts", host->GetMaxCheckAttempts()); + fields->Set("last_check", DbValue::FromTimestamp(host->GetLastCheck())); + fields->Set("next_check", DbValue::FromTimestamp(host->GetNextCheck())); + fields->Set("check_type", !host->GetEnableActiveChecks()); /* 0 .. active, 1 .. passive */ + fields->Set("last_state_change", DbValue::FromTimestamp(host->GetLastStateChange())); + fields->Set("last_hard_state_change", DbValue::FromTimestamp(host->GetLastHardStateChange())); + fields->Set("last_hard_state", host->GetLastHardState()); + fields->Set("last_time_up", DbValue::FromTimestamp(host->GetLastStateUp())); + fields->Set("last_time_down", DbValue::FromTimestamp(host->GetLastStateDown())); + fields->Set("last_time_unreachable", DbValue::FromTimestamp(host->GetLastStateUnreachable())); + fields->Set("state_type", host->GetStateType()); + fields->Set("notifications_enabled", host->GetEnableNotifications()); + fields->Set("problem_has_been_acknowledged", host->GetAcknowledgement() != AcknowledgementNone); + fields->Set("acknowledgement_type", host->GetAcknowledgement()); + fields->Set("passive_checks_enabled", host->GetEnablePassiveChecks()); + fields->Set("active_checks_enabled", host->GetEnableActiveChecks()); + fields->Set("event_handler_enabled", host->GetEnableEventHandler()); + fields->Set("flap_detection_enabled", host->GetEnableFlapping()); + fields->Set("is_flapping", host->IsFlapping()); + fields->Set("percent_state_change", host->GetFlappingCurrent()); + fields->Set("scheduled_downtime_depth", host->GetDowntimeDepth()); + fields->Set("process_performance_data", host->GetEnablePerfdata()); + fields->Set("normal_check_interval", host->GetCheckInterval() / 60.0); + fields->Set("retry_check_interval", host->GetRetryInterval() / 60.0); + fields->Set("check_timeperiod_object_id", host->GetCheckPeriod()); + fields->Set("is_reachable", host->GetLastReachable()); + fields->Set("original_attributes", JsonEncode(host->GetOriginalAttributes())); + + fields->Set("current_notification_number", CompatUtility::GetCheckableNotificationNotificationNumber(host)); + fields->Set("last_notification", DbValue::FromTimestamp(CompatUtility::GetCheckableNotificationLastNotification(host))); + fields->Set("next_notification", DbValue::FromTimestamp(CompatUtility::GetCheckableNotificationNextNotification(host))); + + EventCommand::Ptr eventCommand = host->GetEventCommand(); + + if (eventCommand) + fields->Set("event_handler", eventCommand->GetName()); + + CheckCommand::Ptr checkCommand = host->GetCheckCommand(); + + if (checkCommand) + fields->Set("check_command", checkCommand->GetName()); + + return fields; +} + +void HostDbObject::OnConfigUpdateHeavy() +{ + Host::Ptr host = static_pointer_cast<Host>(GetObject()); + + /* groups */ + Array::Ptr groups = host->GetGroups(); + + std::vector<DbQuery> queries; + + DbQuery query1; + query1.Table = DbType::GetByName("HostGroup")->GetTable() + "_members"; + query1.Type = DbQueryDelete; + query1.Category = DbCatConfig; + query1.WhereCriteria = new Dictionary(); + query1.WhereCriteria->Set("host_object_id", host); + queries.emplace_back(std::move(query1)); + + if (groups) { + ObjectLock olock(groups); + for (const String& groupName : groups) { + HostGroup::Ptr group = HostGroup::GetByName(groupName); + + DbQuery query2; + query2.Table = DbType::GetByName("HostGroup")->GetTable() + "_members"; + query2.Type = DbQueryInsert; + query2.Category = DbCatConfig; + query2.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "hostgroup_id", DbValue::FromObjectInsertID(group) }, + { "host_object_id", host } + }); + query2.WhereCriteria = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "hostgroup_id", DbValue::FromObjectInsertID(group) }, + { "host_object_id", host } + }); + queries.emplace_back(std::move(query2)); + } + } + + DbObject::OnMultipleQueries(queries); + + queries.clear(); + + DbQuery query2; + query2.Table = GetType()->GetTable() + "_parenthosts"; + query2.Type = DbQueryDelete; + query2.Category = DbCatConfig; + query2.WhereCriteria = new Dictionary({ + { GetType()->GetTable() + "_id", DbValue::FromObjectInsertID(GetObject()) } + }); + queries.emplace_back(std::move(query2)); + + /* parents */ + for (const Checkable::Ptr& checkable : host->GetParents()) { + Host::Ptr parent = dynamic_pointer_cast<Host>(checkable); + + if (!parent) + continue; + + Log(LogDebug, "HostDbObject") + << "host parents: " << parent->GetName(); + + /* parents: host_id, parent_host_object_id */ + DbQuery query1; + query1.Table = GetType()->GetTable() + "_parenthosts"; + query1.Type = DbQueryInsert; + query1.Category = DbCatConfig; + query1.Fields = new Dictionary({ + { GetType()->GetTable() + "_id", DbValue::FromObjectInsertID(GetObject()) }, + { "parent_host_object_id", parent }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query1)); + } + + DbObject::OnMultipleQueries(queries); + + /* host dependencies */ + Log(LogDebug, "HostDbObject") + << "host dependencies for '" << host->GetName() << "'"; + + queries.clear(); + + DbQuery query3; + query3.Table = GetType()->GetTable() + "dependencies"; + query3.Type = DbQueryDelete; + query3.Category = DbCatConfig; + query3.WhereCriteria = new Dictionary({ + { "dependent_host_object_id", host } + }); + queries.emplace_back(std::move(query3)); + + for (const Dependency::Ptr& dep : host->GetDependencies()) { + Checkable::Ptr parent = dep->GetParent(); + + if (!parent) { + Log(LogDebug, "HostDbObject") + << "Missing parent for dependency '" << dep->GetName() << "'."; + continue; + } + + int stateFilter = dep->GetStateFilter(); + + Log(LogDebug, "HostDbObject") + << "parent host: " << parent->GetName(); + + DbQuery query2; + query2.Table = GetType()->GetTable() + "dependencies"; + query2.Type = DbQueryInsert; + query2.Category = DbCatConfig; + query2.Fields = new Dictionary({ + { "host_object_id", parent }, + { "dependent_host_object_id", host }, + { "inherits_parent", 1 }, + { "timeperiod_object_id", dep->GetPeriod() }, + { "fail_on_up", (stateFilter & StateFilterUp) ? 1 : 0 }, + { "fail_on_down", (stateFilter & StateFilterDown) ? 1 : 0 }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query2)); + } + + DbObject::OnMultipleQueries(queries); + + Log(LogDebug, "HostDbObject") + << "host contacts: " << host->GetName(); + + queries.clear(); + + DbQuery query4; + query4.Table = GetType()->GetTable() + "_contacts"; + query4.Type = DbQueryDelete; + query4.Category = DbCatConfig; + query4.WhereCriteria = new Dictionary({ + { "host_id", DbValue::FromObjectInsertID(host) } + }); + queries.emplace_back(std::move(query4)); + + for (const User::Ptr& user : CompatUtility::GetCheckableNotificationUsers(host)) { + Log(LogDebug, "HostDbObject") + << "host contacts: " << user->GetName(); + + DbQuery query_contact; + query_contact.Table = GetType()->GetTable() + "_contacts"; + query_contact.Type = DbQueryInsert; + query_contact.Category = DbCatConfig; + query_contact.Fields = new Dictionary({ + { "host_id", DbValue::FromObjectInsertID(host) }, + { "contact_object_id", user }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query_contact)); + } + + DbObject::OnMultipleQueries(queries); + + Log(LogDebug, "HostDbObject") + << "host contactgroups: " << host->GetName(); + + queries.clear(); + + DbQuery query5; + query5.Table = GetType()->GetTable() + "_contactgroups"; + query5.Type = DbQueryDelete; + query5.Category = DbCatConfig; + query5.WhereCriteria = new Dictionary({ + { "host_id", DbValue::FromObjectInsertID(host) } + }); + queries.emplace_back(std::move(query5)); + + for (const UserGroup::Ptr& usergroup : CompatUtility::GetCheckableNotificationUserGroups(host)) { + Log(LogDebug, "HostDbObject") + << "host contactgroups: " << usergroup->GetName(); + + DbQuery query_contact; + query_contact.Table = GetType()->GetTable() + "_contactgroups"; + query_contact.Type = DbQueryInsert; + query_contact.Category = DbCatConfig; + query_contact.Fields = new Dictionary({ + { "host_id", DbValue::FromObjectInsertID(host) }, + { "contactgroup_object_id", usergroup }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query_contact)); + } + + DbObject::OnMultipleQueries(queries); + + DoCommonConfigUpdate(); +} + +void HostDbObject::OnConfigUpdateLight() +{ + DoCommonConfigUpdate(); +} + +void HostDbObject::DoCommonConfigUpdate() +{ + Host::Ptr host = static_pointer_cast<Host>(GetObject()); + + /* update comments and downtimes on config change */ + DbEvents::AddComments(host); + DbEvents::AddDowntimes(host); +} + +String HostDbObject::CalculateConfigHash(const Dictionary::Ptr& configFields) const +{ + String hashData = DbObject::CalculateConfigHash(configFields); + + Host::Ptr host = static_pointer_cast<Host>(GetObject()); + + Array::Ptr groups = host->GetGroups(); + + if (groups) { + groups = groups->ShallowClone(); + ObjectLock oLock (groups); + std::sort(groups->Begin(), groups->End()); + hashData += DbObject::HashValue(groups); + } + + ArrayData parents; + + /* parents */ + for (const Checkable::Ptr& checkable : host->GetParents()) { + Host::Ptr parent = dynamic_pointer_cast<Host>(checkable); + + if (!parent) + continue; + + parents.push_back(parent->GetName()); + } + + std::sort(parents.begin(), parents.end()); + + hashData += DbObject::HashValue(new Array(std::move(parents))); + + ArrayData dependencies; + + /* dependencies */ + for (const Dependency::Ptr& dep : host->GetDependencies()) { + Checkable::Ptr parent = dep->GetParent(); + + if (!parent) + continue; + + dependencies.push_back(new Array({ + parent->GetName(), + dep->GetStateFilter(), + dep->GetPeriodRaw() + })); + } + + std::sort(dependencies.begin(), dependencies.end()); + + hashData += DbObject::HashValue(new Array(std::move(dependencies))); + + ArrayData users; + + for (const User::Ptr& user : CompatUtility::GetCheckableNotificationUsers(host)) { + users.push_back(user->GetName()); + } + + std::sort(users.begin(), users.end()); + + hashData += DbObject::HashValue(new Array(std::move(users))); + + ArrayData userGroups; + + for (const UserGroup::Ptr& usergroup : CompatUtility::GetCheckableNotificationUserGroups(host)) { + userGroups.push_back(usergroup->GetName()); + } + + std::sort(userGroups.begin(), userGroups.end()); + + hashData += DbObject::HashValue(new Array(std::move(userGroups))); + + return SHA256(hashData); +} diff --git a/lib/db_ido/hostdbobject.hpp b/lib/db_ido/hostdbobject.hpp new file mode 100644 index 0000000..9fff10a --- /dev/null +++ b/lib/db_ido/hostdbobject.hpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef HOSTDBOBJECT_H +#define HOSTDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A Host database object. + * + * @ingroup ido + */ +class HostDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(HostDbObject); + + HostDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; + + void OnConfigUpdateHeavy() override; + void OnConfigUpdateLight() override; + + String CalculateConfigHash(const Dictionary::Ptr& configFields) const override; + +private: + void DoCommonConfigUpdate(); +}; + +} + +#endif /* HOSTDBOBJECT_H */ diff --git a/lib/db_ido/hostgroupdbobject.cpp b/lib/db_ido/hostgroupdbobject.cpp new file mode 100644 index 0000000..cef6aa2 --- /dev/null +++ b/lib/db_ido/hostgroupdbobject.cpp @@ -0,0 +1,33 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/hostgroupdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "base/objectlock.hpp" +#include "base/initialize.hpp" +#include "base/configtype.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(HostGroup, "hostgroup", DbObjectTypeHostGroup, "hostgroup_object_id", HostGroupDbObject); + +HostGroupDbObject::HostGroupDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr HostGroupDbObject::GetConfigFields() const +{ + HostGroup::Ptr group = static_pointer_cast<HostGroup>(GetObject()); + + return new Dictionary({ + { "alias", group->GetDisplayName() }, + { "notes", group->GetNotes() }, + { "notes_url", group->GetNotesUrl() }, + { "action_url", group->GetActionUrl() } + }); +} + +Dictionary::Ptr HostGroupDbObject::GetStatusFields() const +{ + return nullptr; +} diff --git a/lib/db_ido/hostgroupdbobject.hpp b/lib/db_ido/hostgroupdbobject.hpp new file mode 100644 index 0000000..9c48f29 --- /dev/null +++ b/lib/db_ido/hostgroupdbobject.hpp @@ -0,0 +1,34 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef HOSTGROUPDBOBJECT_H +#define HOSTGROUPDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "icinga/hostgroup.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A HostGroup database object. + * + * @ingroup ido + */ +class HostGroupDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(HostGroupDbObject); + + HostGroupDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; + +private: + static void MembersChangedHandler(const HostGroup::Ptr& hgfilter); +}; + +} + +#endif /* HOSTGROUPDBOBJECT_H */ diff --git a/lib/db_ido/i2-db_ido.hpp b/lib/db_ido/i2-db_ido.hpp new file mode 100644 index 0000000..1da9fdc --- /dev/null +++ b/lib/db_ido/i2-db_ido.hpp @@ -0,0 +1,14 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef I2DB_IDO_H +#define I2DB_IDO_H + +/** + * @defgroup db_ido IDO library + * + * The Icinga library implements database-agnostic IDO functionality. + */ + +#include "base/i2-base.hpp" + +#endif /* I2DB_IDO_H */ diff --git a/lib/db_ido/idochecktask.cpp b/lib/db_ido/idochecktask.cpp new file mode 100644 index 0000000..3b5856a --- /dev/null +++ b/lib/db_ido/idochecktask.cpp @@ -0,0 +1,197 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/idochecktask.hpp" +#include "icinga/host.hpp" +#include "icinga/checkcommand.hpp" +#include "icinga/macroprocessor.hpp" +#include "remote/apilistener.hpp" +#include "remote/endpoint.hpp" +#include "remote/zone.hpp" +#include "base/function.hpp" +#include "base/utility.hpp" +#include "base/perfdatavalue.hpp" +#include "base/configtype.hpp" +#include "base/convert.hpp" +#include <utility> + +using namespace icinga; + +REGISTER_FUNCTION_NONCONST(Internal, IdoCheck, &IdoCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); + +static void ReportIdoCheck( + const Checkable::Ptr& checkable, const CheckCommand::Ptr& commandObj, + const CheckResult::Ptr& cr, String output, ServiceState state = ServiceUnknown +) +{ + if (Checkable::ExecuteCommandProcessFinishedHandler) { + double now = Utility::GetTime(); + ProcessResult pr; + pr.PID = -1; + pr.Output = std::move(output); + pr.ExecutionStart = now; + pr.ExecutionEnd = now; + pr.ExitStatus = state; + + Checkable::ExecuteCommandProcessFinishedHandler(commandObj->GetName(), pr); + } else { + cr->SetState(state); + cr->SetOutput(output); + checkable->ProcessCheckResult(cr); + } +} + +void IdoCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, + const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) +{ + ServiceState state; + CheckCommand::Ptr commandObj = CheckCommand::ExecuteOverride ? CheckCommand::ExecuteOverride : checkable->GetCheckCommand(); + Value raw_command = commandObj->GetCommandLine(); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + MacroProcessor::ResolverList resolvers; + + if (MacroResolver::OverrideMacros) + resolvers.emplace_back("override", MacroResolver::OverrideMacros); + + if (service) + resolvers.emplace_back("service", service); + resolvers.emplace_back("host", host); + resolvers.emplace_back("command", commandObj); + + String idoType = MacroProcessor::ResolveMacros("$ido_type$", resolvers, checkable->GetLastCheckResult(), + nullptr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + String idoName = MacroProcessor::ResolveMacros("$ido_name$", resolvers, checkable->GetLastCheckResult(), + nullptr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + String missingQueriesWarning; + String missingQueriesCritical; + String missingPendingQueriesWarning; + String missingPendingQueriesCritical; + + double queriesWarning = MacroProcessor::ResolveMacros("$ido_queries_warning$", resolvers, checkable->GetLastCheckResult(), + &missingQueriesWarning, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + double queriesCritical = MacroProcessor::ResolveMacros("$ido_queries_critical$", resolvers, checkable->GetLastCheckResult(), + &missingQueriesCritical, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + double pendingQueriesWarning = MacroProcessor::ResolveMacros("$ido_pending_queries_warning$", resolvers, checkable->GetLastCheckResult(), + &missingPendingQueriesWarning, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + double pendingQueriesCritical = MacroProcessor::ResolveMacros("$ido_pending_queries_critical$", resolvers, checkable->GetLastCheckResult(), + &missingPendingQueriesCritical, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + if (resolvedMacros && !useResolvedMacros) + return; + + if (idoType.IsEmpty()) { + ReportIdoCheck(checkable, commandObj, cr, "Attribute 'ido_type' must be set."); + return; + } + + if (idoName.IsEmpty()) { + ReportIdoCheck(checkable, commandObj, cr, "Attribute 'ido_name' must be set."); + return; + } + + Type::Ptr type = Type::GetByName(idoType); + + if (!type || !DbConnection::TypeInstance->IsAssignableFrom(type)) { + ReportIdoCheck(checkable, commandObj, cr, "DB IDO type '" + idoType + "' is invalid."); + return; + } + + auto *dtype = dynamic_cast<ConfigType *>(type.get()); + VERIFY(dtype); + + DbConnection::Ptr conn = static_pointer_cast<DbConnection>(dtype->GetObject(idoName)); + + if (!conn) { + ReportIdoCheck(checkable, commandObj, cr, "DB IDO connection '" + idoName + "' does not exist."); + return; + } + + double qps = conn->GetQueryCount(60) / 60.0; + + if (conn->IsPaused()) { + ReportIdoCheck(checkable, commandObj, cr, "DB IDO connection is temporarily disabled on this cluster instance.", ServiceOK); + return; + } + + double pendingQueries = conn->GetPendingQueryCount(); + + if (!conn->GetConnected()) { + if (conn->GetShouldConnect()) { + ReportIdoCheck(checkable, commandObj, cr, "Could not connect to the database server.", ServiceCritical); + } else { + ReportIdoCheck( + checkable, commandObj, cr, + "Not currently enabled: Another cluster instance is responsible for the IDO database.", ServiceOK + ); + } + return; + } + + /* Schema versions. */ + String schema_version = conn->GetSchemaVersion(); + std::ostringstream msgbuf; + + if (Utility::CompareVersion(conn->GetLatestSchemaVersion(), schema_version) < 0) { + msgbuf << "Outdated schema version: '" << schema_version << "'. Latest version: '" + << conn->GetLatestSchemaVersion() << "'." + << " Queries per second: " << std::fixed << std::setprecision(3) << qps + << " Pending queries: " << std::fixed << std::setprecision(3) << pendingQueries << "."; + + state = ServiceWarning; + } else { + msgbuf << "Connected to the database server (Schema version: '" << schema_version << "')." + << " Queries per second: " << std::fixed << std::setprecision(3) << qps + << " Pending queries: " << std::fixed << std::setprecision(3) << pendingQueries << "."; + + state = ServiceOK; + } + + if (conn->GetEnableHa()) { + double failoverTs = conn->GetLastFailover(); + + msgbuf << " Last failover: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", failoverTs) << "."; + } + + /* Check whether the thresholds have been defined and match. */ + if (missingQueriesCritical.IsEmpty() && qps < queriesCritical) { + msgbuf << " " << qps << " queries/s lower than critical threshold (" << queriesCritical << " queries/s)."; + + state = ServiceCritical; + } else if (missingQueriesWarning.IsEmpty() && qps < queriesWarning) { + msgbuf << " " << qps << " queries/s lower than warning threshold (" << queriesWarning << " queries/s)."; + + state = ServiceWarning; + } + + if (missingPendingQueriesCritical.IsEmpty() && pendingQueries > pendingQueriesCritical) { + msgbuf << " " << pendingQueries << " pending queries greater than critical threshold (" + << pendingQueriesCritical << " queries)."; + + state = ServiceCritical; + } else if (missingPendingQueriesWarning.IsEmpty() && pendingQueries > pendingQueriesWarning) { + msgbuf << " " << pendingQueries << " pending queries greater than warning threshold (" + << pendingQueriesWarning << " queries)."; + + if (state == ServiceOK) { + state = ServiceWarning; + } + } + + cr->SetPerformanceData(new Array({ + { new PerfdataValue("queries", qps, false, "", queriesWarning, queriesCritical) }, + { new PerfdataValue("queries_1min", conn->GetQueryCount(60)) }, + { new PerfdataValue("queries_5mins", conn->GetQueryCount(5 * 60)) }, + { new PerfdataValue("queries_15mins", conn->GetQueryCount(15 * 60)) }, + { new PerfdataValue("pending_queries", pendingQueries, false, "", pendingQueriesWarning, pendingQueriesCritical) } + })); + + ReportIdoCheck(checkable, commandObj, cr, msgbuf.str(), state); +} diff --git a/lib/db_ido/idochecktask.hpp b/lib/db_ido/idochecktask.hpp new file mode 100644 index 0000000..5868c38 --- /dev/null +++ b/lib/db_ido/idochecktask.hpp @@ -0,0 +1,29 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef IDOCHECKTASK_H +#define IDOCHECKTASK_H + +#include "db_ido/dbconnection.hpp" +#include "icinga/checkable.hpp" + +namespace icinga +{ + +/** + * IDO check type. + * + * @ingroup db_ido + */ +class IdoCheckTask +{ +public: + static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, + const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + +private: + IdoCheckTask(); +}; + +} + +#endif /* IDOCHECKTASK_H */ diff --git a/lib/db_ido/servicedbobject.cpp b/lib/db_ido/servicedbobject.cpp new file mode 100644 index 0000000..7f711df --- /dev/null +++ b/lib/db_ido/servicedbobject.cpp @@ -0,0 +1,359 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/servicedbobject.hpp" +#include "db_ido/servicegroupdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "db_ido/dbevents.hpp" +#include "icinga/notification.hpp" +#include "icinga/dependency.hpp" +#include "icinga/checkcommand.hpp" +#include "icinga/eventcommand.hpp" +#include "icinga/externalcommandprocessor.hpp" +#include "icinga/compatutility.hpp" +#include "icinga/pluginutility.hpp" +#include "icinga/icingaapplication.hpp" +#include "remote/endpoint.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/initialize.hpp" +#include "base/configtype.hpp" +#include "base/utility.hpp" +#include "base/logger.hpp" +#include "base/json.hpp" +#include <boost/algorithm/string/join.hpp> + +using namespace icinga; + +REGISTER_DBTYPE(Service, "service", DbObjectTypeService, "service_object_id", ServiceDbObject); + +ServiceDbObject::ServiceDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr ServiceDbObject::GetConfigFields() const +{ + Service::Ptr service = static_pointer_cast<Service>(GetObject()); + Host::Ptr host = service->GetHost(); + + unsigned long notificationStateFilter = CompatUtility::GetCheckableNotificationTypeFilter(service); + unsigned long notificationTypeFilter = CompatUtility::GetCheckableNotificationTypeFilter(service); + + return new Dictionary({ + { "host_object_id", host }, + { "display_name", service->GetDisplayName() }, + { "check_command_object_id", service->GetCheckCommand() }, + { "eventhandler_command_object_id", service->GetEventCommand() }, + { "check_timeperiod_object_id", service->GetCheckPeriod() }, + { "check_interval", service->GetCheckInterval() / 60.0 }, + { "retry_interval", service->GetRetryInterval() / 60.0 }, + { "max_check_attempts", service->GetMaxCheckAttempts() }, + { "is_volatile", service->GetVolatile() }, + { "flap_detection_enabled", service->GetEnableFlapping() }, + { "low_flap_threshold", service->GetFlappingThresholdLow() }, + { "high_flap_threshold", service->GetFlappingThresholdLow() }, + { "process_performance_data", service->GetEnablePerfdata() }, + { "freshness_checks_enabled", 1 }, + { "freshness_threshold", Convert::ToLong(service->GetCheckInterval()) }, + { "event_handler_enabled", service->GetEnableEventHandler() }, + { "passive_checks_enabled", service->GetEnablePassiveChecks() }, + { "active_checks_enabled", service->GetEnableActiveChecks() }, + { "notifications_enabled", service->GetEnableNotifications() }, + { "notes", service->GetNotes() }, + { "notes_url", service->GetNotesUrl() }, + { "action_url", service->GetActionUrl() }, + { "icon_image", service->GetIconImage() }, + { "icon_image_alt", service->GetIconImageAlt() }, + { "notification_interval", CompatUtility::GetCheckableNotificationNotificationInterval(service) }, + { "notify_on_warning", (notificationStateFilter & ServiceWarning) ? 1 : 0 }, + { "notify_on_unknown", (notificationStateFilter & ServiceUnknown) ? 1 : 0 }, + { "notify_on_critical", (notificationStateFilter & ServiceCritical) ? 1 : 0 }, + { "notify_on_recovery", (notificationTypeFilter & NotificationRecovery) ? 1 : 0 }, + { "notify_on_flapping", (notificationTypeFilter & (NotificationFlappingStart | NotificationFlappingEnd)) ? 1 : 0 }, + { "notify_on_downtime", (notificationTypeFilter & (NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved)) ? 1 : 0 } + }); +} + +Dictionary::Ptr ServiceDbObject::GetStatusFields() const +{ + Dictionary::Ptr fields = new Dictionary(); + Service::Ptr service = static_pointer_cast<Service>(GetObject()); + CheckResult::Ptr cr = service->GetLastCheckResult(); + + if (cr) { + fields->Set("output", CompatUtility::GetCheckResultOutput(cr)); + fields->Set("long_output", CompatUtility::GetCheckResultLongOutput(cr)); + fields->Set("perfdata", PluginUtility::FormatPerfdata(cr->GetPerformanceData())); + fields->Set("check_source", cr->GetCheckSource()); + fields->Set("latency", cr->CalculateLatency()); + fields->Set("execution_time", cr->CalculateExecutionTime()); + } + + fields->Set("current_state", service->GetState()); + fields->Set("has_been_checked", service->HasBeenChecked()); + fields->Set("should_be_scheduled", service->GetEnableActiveChecks()); + fields->Set("current_check_attempt", service->GetCheckAttempt()); + fields->Set("max_check_attempts", service->GetMaxCheckAttempts()); + fields->Set("last_check", DbValue::FromTimestamp(service->GetLastCheck())); + fields->Set("next_check", DbValue::FromTimestamp(service->GetNextCheck())); + fields->Set("check_type", !service->GetEnableActiveChecks()); /* 0 .. active, 1 .. passive */ + fields->Set("last_state_change", DbValue::FromTimestamp(service->GetLastStateChange())); + fields->Set("last_hard_state_change", DbValue::FromTimestamp(service->GetLastHardStateChange())); + fields->Set("last_hard_state", service->GetLastHardState()); + fields->Set("last_time_ok", DbValue::FromTimestamp(service->GetLastStateOK())); + fields->Set("last_time_warning", DbValue::FromTimestamp(service->GetLastStateWarning())); + fields->Set("last_time_critical", DbValue::FromTimestamp(service->GetLastStateCritical())); + fields->Set("last_time_unknown", DbValue::FromTimestamp(service->GetLastStateUnknown())); + fields->Set("state_type", service->GetStateType()); + fields->Set("notifications_enabled", service->GetEnableNotifications()); + fields->Set("problem_has_been_acknowledged", service->GetAcknowledgement() != AcknowledgementNone); + fields->Set("acknowledgement_type", service->GetAcknowledgement()); + fields->Set("passive_checks_enabled", service->GetEnablePassiveChecks()); + fields->Set("active_checks_enabled", service->GetEnableActiveChecks()); + fields->Set("event_handler_enabled", service->GetEnableEventHandler()); + fields->Set("flap_detection_enabled", service->GetEnableFlapping()); + fields->Set("is_flapping", service->IsFlapping()); + fields->Set("percent_state_change", service->GetFlappingCurrent()); + fields->Set("scheduled_downtime_depth", service->GetDowntimeDepth()); + fields->Set("process_performance_data", service->GetEnablePerfdata()); + fields->Set("normal_check_interval", service->GetCheckInterval() / 60.0); + fields->Set("retry_check_interval", service->GetRetryInterval() / 60.0); + fields->Set("check_timeperiod_object_id", service->GetCheckPeriod()); + fields->Set("is_reachable", service->GetLastReachable()); + fields->Set("original_attributes", JsonEncode(service->GetOriginalAttributes())); + + fields->Set("current_notification_number", CompatUtility::GetCheckableNotificationNotificationNumber(service)); + fields->Set("last_notification", DbValue::FromTimestamp(CompatUtility::GetCheckableNotificationLastNotification(service))); + fields->Set("next_notification", DbValue::FromTimestamp(CompatUtility::GetCheckableNotificationNextNotification(service))); + + EventCommand::Ptr eventCommand = service->GetEventCommand(); + + if (eventCommand) + fields->Set("event_handler", eventCommand->GetName()); + + CheckCommand::Ptr checkCommand = service->GetCheckCommand(); + + if (checkCommand) + fields->Set("check_command", checkCommand->GetName()); + + return fields; +} + +void ServiceDbObject::OnConfigUpdateHeavy() +{ + Service::Ptr service = static_pointer_cast<Service>(GetObject()); + + /* groups */ + Array::Ptr groups = service->GetGroups(); + + std::vector<DbQuery> queries; + + DbQuery query1; + query1.Table = DbType::GetByName("ServiceGroup")->GetTable() + "_members"; + query1.Type = DbQueryDelete; + query1.Category = DbCatConfig; + query1.WhereCriteria = new Dictionary({ + { "service_object_id", service } + }); + queries.emplace_back(std::move(query1)); + + if (groups) { + ObjectLock olock(groups); + for (const String& groupName : groups) { + ServiceGroup::Ptr group = ServiceGroup::GetByName(groupName); + + DbQuery query2; + query2.Table = DbType::GetByName("ServiceGroup")->GetTable() + "_members"; + query2.Type = DbQueryInsert; + query2.Category = DbCatConfig; + query2.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "servicegroup_id", DbValue::FromObjectInsertID(group) }, + { "service_object_id", service } + }); + query2.WhereCriteria = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "servicegroup_id", DbValue::FromObjectInsertID(group) }, + { "service_object_id", service } + }); + queries.emplace_back(std::move(query2)); + } + } + + DbObject::OnMultipleQueries(queries); + + /* service dependencies */ + queries.clear(); + + DbQuery query2; + query2.Table = GetType()->GetTable() + "dependencies"; + query2.Type = DbQueryDelete; + query2.Category = DbCatConfig; + query2.WhereCriteria = new Dictionary({ + { "dependent_service_object_id", service } + }); + queries.emplace_back(std::move(query2)); + + for (const Dependency::Ptr& dep : service->GetDependencies()) { + Checkable::Ptr parent = dep->GetParent(); + + if (!parent) { + Log(LogDebug, "ServiceDbObject") + << "Missing parent for dependency '" << dep->GetName() << "'."; + continue; + } + + Log(LogDebug, "ServiceDbObject") + << "service parents: " << parent->GetName(); + + int stateFilter = dep->GetStateFilter(); + + /* service dependencies */ + DbQuery query1; + query1.Table = GetType()->GetTable() + "dependencies"; + query1.Type = DbQueryInsert; + query1.Category = DbCatConfig; + query1.Fields = new Dictionary({ + { "service_object_id", parent }, + { "dependent_service_object_id", service }, + { "inherits_parent", 1 }, + { "timeperiod_object_id", dep->GetPeriod() }, + { "fail_on_ok", (stateFilter & StateFilterOK) ? 1 : 0 }, + { "fail_on_warning", (stateFilter & StateFilterWarning) ? 1 : 0 }, + { "fail_on_critical", (stateFilter & StateFilterCritical) ? 1 : 0 }, + { "fail_on_unknown", (stateFilter & StateFilterUnknown) ? 1 : 0 }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query1)); + } + + DbObject::OnMultipleQueries(queries); + + /* service contacts, contactgroups */ + queries.clear(); + + DbQuery query3; + query3.Table = GetType()->GetTable() + "_contacts"; + query3.Type = DbQueryDelete; + query3.Category = DbCatConfig; + query3.WhereCriteria = new Dictionary({ + { "service_id", DbValue::FromObjectInsertID(service) } + }); + queries.emplace_back(std::move(query3)); + + for (const User::Ptr& user : CompatUtility::GetCheckableNotificationUsers(service)) { + DbQuery query_contact; + query_contact.Table = GetType()->GetTable() + "_contacts"; + query_contact.Type = DbQueryInsert; + query_contact.Category = DbCatConfig; + query_contact.Fields = new Dictionary({ + { "service_id", DbValue::FromObjectInsertID(service) }, + { "contact_object_id", user }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + + }); + queries.emplace_back(std::move(query_contact)); + } + + DbObject::OnMultipleQueries(queries); + + queries.clear(); + + DbQuery query4; + query4.Table = GetType()->GetTable() + "_contactgroups"; + query4.Type = DbQueryDelete; + query4.Category = DbCatConfig; + query4.WhereCriteria = new Dictionary({ + { "service_id", DbValue::FromObjectInsertID(service) } + }); + queries.emplace_back(std::move(query4)); + + for (const UserGroup::Ptr& usergroup : CompatUtility::GetCheckableNotificationUserGroups(service)) { + DbQuery query_contact; + query_contact.Table = GetType()->GetTable() + "_contactgroups"; + query_contact.Type = DbQueryInsert; + query_contact.Category = DbCatConfig; + query_contact.Fields = new Dictionary({ + { "service_id", DbValue::FromObjectInsertID(service) }, + { "contactgroup_object_id", usergroup }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + }); + queries.emplace_back(std::move(query_contact)); + } + + DbObject::OnMultipleQueries(queries); + + DoCommonConfigUpdate(); +} + +void ServiceDbObject::OnConfigUpdateLight() +{ + DoCommonConfigUpdate(); +} + +void ServiceDbObject::DoCommonConfigUpdate() +{ + Service::Ptr service = static_pointer_cast<Service>(GetObject()); + + /* update comments and downtimes on config change */ + DbEvents::AddComments(service); + DbEvents::AddDowntimes(service); +} + +String ServiceDbObject::CalculateConfigHash(const Dictionary::Ptr& configFields) const +{ + String hashData = DbObject::CalculateConfigHash(configFields); + + Service::Ptr service = static_pointer_cast<Service>(GetObject()); + + Array::Ptr groups = service->GetGroups(); + + if (groups) { + groups = groups->ShallowClone(); + ObjectLock oLock (groups); + std::sort(groups->Begin(), groups->End()); + hashData += DbObject::HashValue(groups); + } + + ArrayData dependencies; + + /* dependencies */ + for (const Dependency::Ptr& dep : service->GetDependencies()) { + Checkable::Ptr parent = dep->GetParent(); + + if (!parent) + continue; + + dependencies.push_back(new Array({ + parent->GetName(), + dep->GetStateFilter(), + dep->GetPeriodRaw() + })); + } + + std::sort(dependencies.begin(), dependencies.end()); + + hashData += DbObject::HashValue(new Array(std::move(dependencies))); + + ArrayData users; + + for (const User::Ptr& user : CompatUtility::GetCheckableNotificationUsers(service)) { + users.push_back(user->GetName()); + } + + std::sort(users.begin(), users.end()); + + hashData += DbObject::HashValue(new Array(std::move(users))); + + ArrayData userGroups; + + for (const UserGroup::Ptr& usergroup : CompatUtility::GetCheckableNotificationUserGroups(service)) { + userGroups.push_back(usergroup->GetName()); + } + + std::sort(userGroups.begin(), userGroups.end()); + + hashData += DbObject::HashValue(new Array(std::move(userGroups))); + + return SHA256(hashData); +} diff --git a/lib/db_ido/servicedbobject.hpp b/lib/db_ido/servicedbobject.hpp new file mode 100644 index 0000000..19824be --- /dev/null +++ b/lib/db_ido/servicedbobject.hpp @@ -0,0 +1,41 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SERVICEDBOBJECT_H +#define SERVICEDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" +#include "icinga/service.hpp" + +namespace icinga +{ + +/** + * A Service database object. + * + * @ingroup ido + */ +class ServiceDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(ServiceDbObject); + + ServiceDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + + static void StaticInitialize(); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; + + void OnConfigUpdateHeavy() override; + void OnConfigUpdateLight() override; + + String CalculateConfigHash(const Dictionary::Ptr& configFields) const override; + +private: + void DoCommonConfigUpdate(); +}; + +} + +#endif /* SERVICEDBOBJECT_H */ diff --git a/lib/db_ido/servicegroupdbobject.cpp b/lib/db_ido/servicegroupdbobject.cpp new file mode 100644 index 0000000..ea4d40c --- /dev/null +++ b/lib/db_ido/servicegroupdbobject.cpp @@ -0,0 +1,32 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/servicegroupdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "base/objectlock.hpp" +#include "base/initialize.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(ServiceGroup, "servicegroup", DbObjectTypeServiceGroup, "servicegroup_object_id", ServiceGroupDbObject); + +ServiceGroupDbObject::ServiceGroupDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr ServiceGroupDbObject::GetConfigFields() const +{ + ServiceGroup::Ptr group = static_pointer_cast<ServiceGroup>(GetObject()); + + return new Dictionary({ + { "alias", group->GetDisplayName() }, + { "notes", group->GetNotes() }, + { "notes_url", group->GetNotesUrl() }, + { "action_url", group->GetActionUrl() } + }); +} + +Dictionary::Ptr ServiceGroupDbObject::GetStatusFields() const +{ + return nullptr; +} diff --git a/lib/db_ido/servicegroupdbobject.hpp b/lib/db_ido/servicegroupdbobject.hpp new file mode 100644 index 0000000..7f0d6c1 --- /dev/null +++ b/lib/db_ido/servicegroupdbobject.hpp @@ -0,0 +1,31 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef SERVICEGROUPDBOBJECT_H +#define SERVICEGROUPDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "icinga/servicegroup.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A ServiceGroup database object. + * + * @ingroup ido + */ +class ServiceGroupDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(ServiceGroupDbObject); + + ServiceGroupDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; +}; + +} + +#endif /* SERVICEGROUPDBOBJECT_H */ diff --git a/lib/db_ido/timeperioddbobject.cpp b/lib/db_ido/timeperioddbobject.cpp new file mode 100644 index 0000000..98997f5 --- /dev/null +++ b/lib/db_ido/timeperioddbobject.cpp @@ -0,0 +1,85 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/timeperioddbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/timeperiod.hpp" +#include "icinga/legacytimeperiod.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include "base/objectlock.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(TimePeriod, "timeperiod", DbObjectTypeTimePeriod, "timeperiod_object_id", TimePeriodDbObject); + +TimePeriodDbObject::TimePeriodDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr TimePeriodDbObject::GetConfigFields() const +{ + TimePeriod::Ptr tp = static_pointer_cast<TimePeriod>(GetObject()); + + return new Dictionary({ + { "alias", tp->GetDisplayName() } + }); +} + +Dictionary::Ptr TimePeriodDbObject::GetStatusFields() const +{ + return Empty; +} + +void TimePeriodDbObject::OnConfigUpdateHeavy() +{ + TimePeriod::Ptr tp = static_pointer_cast<TimePeriod>(GetObject()); + + DbQuery query_del1; + query_del1.Table = GetType()->GetTable() + "_timeranges"; + query_del1.Type = DbQueryDelete; + query_del1.Category = DbCatConfig; + query_del1.WhereCriteria = new Dictionary({ + { "timeperiod_id", DbValue::FromObjectInsertID(tp) } + }); + OnQuery(query_del1); + + Dictionary::Ptr ranges = tp->GetRanges(); + + if (!ranges) + return; + + time_t refts = Utility::GetTime(); + ObjectLock olock(ranges); + for (const Dictionary::Pair& kv : ranges) { + int wday = LegacyTimePeriod::WeekdayFromString(kv.first); + + if (wday == -1) + continue; + + tm reference = Utility::LocalTime(refts); + + Array::Ptr segments = new Array(); + LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments); + + ObjectLock olock(segments); + for (const Value& vsegment : segments) { + Dictionary::Ptr segment = vsegment; + int begin = segment->Get("begin"); + int end = segment->Get("end"); + + DbQuery query; + query.Table = GetType()->GetTable() + "_timeranges"; + query.Type = DbQueryInsert; + query.Category = DbCatConfig; + query.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "timeperiod_id", DbValue::FromObjectInsertID(tp) }, + { "day", wday }, + { "start_sec", begin % 86400 }, + { "end_sec", end % 86400 } + }); + OnQuery(query); + } + } +} diff --git a/lib/db_ido/timeperioddbobject.hpp b/lib/db_ido/timeperioddbobject.hpp new file mode 100644 index 0000000..e3cc13c --- /dev/null +++ b/lib/db_ido/timeperioddbobject.hpp @@ -0,0 +1,33 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef TIMEPERIODDBOBJECT_H +#define TIMEPERIODDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A TimePeriod database object. + * + * @ingroup ido + */ +class TimePeriodDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(TimePeriodDbObject); + + TimePeriodDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + +protected: + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; + + void OnConfigUpdateHeavy() override; +}; + +} + +#endif /* TIMEPERIODDBOBJECT_H */ diff --git a/lib/db_ido/userdbobject.cpp b/lib/db_ido/userdbobject.cpp new file mode 100644 index 0000000..439b8fb --- /dev/null +++ b/lib/db_ido/userdbobject.cpp @@ -0,0 +1,161 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/userdbobject.hpp" +#include "db_ido/usergroupdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "icinga/user.hpp" +#include "icinga/notification.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include "base/logger.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(User, "contact", DbObjectTypeContact, "contact_object_id", UserDbObject); + +UserDbObject::UserDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr UserDbObject::GetConfigFields() const +{ + User::Ptr user = static_pointer_cast<User>(GetObject()); + + int typeFilter = user->GetTypeFilter(); + int stateFilter = user->GetStateFilter(); + + return new Dictionary({ + { "alias", user->GetDisplayName() }, + { "email_address", user->GetEmail() }, + { "pager_address", user->GetPager() }, + { "host_timeperiod_object_id", user->GetPeriod() }, + { "service_timeperiod_object_id", user->GetPeriod() }, + { "host_notifications_enabled", user->GetEnableNotifications() }, + { "service_notifications_enabled", user->GetEnableNotifications() }, + { "can_submit_commands", 1 }, + { "notify_service_recovery", (typeFilter & NotificationRecovery) ? 1 : 0 }, + { "notify_service_warning", (stateFilter & StateFilterWarning) ? 1 : 0 }, + { "notify_service_unknown", (stateFilter & StateFilterUnknown) ? 1 : 0 }, + { "notify_service_critical", (stateFilter & StateFilterCritical) ? 1 : 0 }, + { "notify_service_flapping", (typeFilter & (NotificationFlappingStart | NotificationFlappingEnd)) ? 1 : 0 }, + { "notify_service_downtime", (typeFilter & (NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved)) ? 1 : 0 }, + { "notify_host_recovery", (typeFilter & NotificationRecovery) ? 1 : 0 }, + { "notify_host_down", (stateFilter & StateFilterDown) ? 1 : 0 }, + { "notify_host_flapping", (typeFilter & (NotificationFlappingStart | NotificationFlappingEnd)) ? 1 : 0 }, + { "notify_host_downtime", (typeFilter & (NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved)) ? 1 : 0 } + }); +} + +Dictionary::Ptr UserDbObject::GetStatusFields() const +{ + User::Ptr user = static_pointer_cast<User>(GetObject()); + + return new Dictionary({ + { "host_notifications_enabled", user->GetEnableNotifications() }, + { "service_notifications_enabled", user->GetEnableNotifications() }, + { "last_host_notification", DbValue::FromTimestamp(user->GetLastNotification()) }, + { "last_service_notification", DbValue::FromTimestamp(user->GetLastNotification()) } + }); +} + +void UserDbObject::OnConfigUpdateHeavy() +{ + User::Ptr user = static_pointer_cast<User>(GetObject()); + + /* groups */ + Array::Ptr groups = user->GetGroups(); + + std::vector<DbQuery> queries; + + DbQuery query1; + query1.Table = DbType::GetByName("UserGroup")->GetTable() + "_members"; + query1.Type = DbQueryDelete; + query1.Category = DbCatConfig; + query1.WhereCriteria = new Dictionary({ + { "contact_object_id", user } + }); + queries.emplace_back(std::move(query1)); + + if (groups) { + ObjectLock olock(groups); + for (const String& groupName : groups) { + UserGroup::Ptr group = UserGroup::GetByName(groupName); + + DbQuery query2; + query2.Table = DbType::GetByName("UserGroup")->GetTable() + "_members"; + query2.Type = DbQueryInsert | DbQueryUpdate; + query2.Category = DbCatConfig; + query2.Fields = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "contactgroup_id", DbValue::FromObjectInsertID(group) }, + { "contact_object_id", user } + }); + query2.WhereCriteria = new Dictionary({ + { "instance_id", 0 }, /* DbConnection class fills in real ID */ + { "contactgroup_id", DbValue::FromObjectInsertID(group) }, + { "contact_object_id", user } + }); + queries.emplace_back(std::move(query2)); + } + } + + DbObject::OnMultipleQueries(queries); + + queries.clear(); + + DbQuery query2; + query2.Table = "contact_addresses"; + query2.Type = DbQueryDelete; + query2.Category = DbCatConfig; + query2.WhereCriteria = new Dictionary({ + { "contact_id", DbValue::FromObjectInsertID(user) } + }); + queries.emplace_back(std::move(query2)); + + Dictionary::Ptr vars = user->GetVars(); + + if (vars) { /* This is sparta. */ + for (int i = 1; i <= 6; i++) { + String key = "address" + Convert::ToString(i); + + if (!vars->Contains(key)) + continue; + + String val = vars->Get(key); + + DbQuery query; + query.Type = DbQueryInsert; + query.Table = "contact_addresses"; + query.Category = DbCatConfig; + query.Fields = new Dictionary({ + { "contact_id", DbValue::FromObjectInsertID(user) }, + { "address_number", i }, + { "address", val }, + { "instance_id", 0 } /* DbConnection class fills in real ID */ + + }); + queries.emplace_back(std::move(query)); + } + } + + DbObject::OnMultipleQueries(queries); +} + +String UserDbObject::CalculateConfigHash(const Dictionary::Ptr& configFields) const +{ + String hashData = DbObject::CalculateConfigHash(configFields); + + User::Ptr user = static_pointer_cast<User>(GetObject()); + + Array::Ptr groups = user->GetGroups(); + + if (groups) { + groups = groups->ShallowClone(); + ObjectLock oLock (groups); + std::sort(groups->Begin(), groups->End()); + hashData += DbObject::HashValue(groups); + } + + return SHA256(hashData); +} diff --git a/lib/db_ido/userdbobject.hpp b/lib/db_ido/userdbobject.hpp new file mode 100644 index 0000000..e0f36c5 --- /dev/null +++ b/lib/db_ido/userdbobject.hpp @@ -0,0 +1,35 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef USERDBOBJECT_H +#define USERDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A User database object. + * + * @ingroup ido + */ +class UserDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(UserDbObject); + + UserDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + +protected: + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; + + void OnConfigUpdateHeavy() override; + + String CalculateConfigHash(const Dictionary::Ptr& configFields) const override; +}; + +} + +#endif /* USERDBOBJECT_H */ diff --git a/lib/db_ido/usergroupdbobject.cpp b/lib/db_ido/usergroupdbobject.cpp new file mode 100644 index 0000000..23b3581 --- /dev/null +++ b/lib/db_ido/usergroupdbobject.cpp @@ -0,0 +1,30 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/usergroupdbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "base/objectlock.hpp" +#include "base/initialize.hpp" +#include "base/configtype.hpp" + +using namespace icinga; + +REGISTER_DBTYPE(UserGroup, "contactgroup", DbObjectTypeContactGroup, "contactgroup_object_id", UserGroupDbObject); + +UserGroupDbObject::UserGroupDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr UserGroupDbObject::GetConfigFields() const +{ + UserGroup::Ptr group = static_pointer_cast<UserGroup>(GetObject()); + + return new Dictionary({ + { "alias", group->GetDisplayName() } + }); +} + +Dictionary::Ptr UserGroupDbObject::GetStatusFields() const +{ + return nullptr; +} diff --git a/lib/db_ido/usergroupdbobject.hpp b/lib/db_ido/usergroupdbobject.hpp new file mode 100644 index 0000000..9469823 --- /dev/null +++ b/lib/db_ido/usergroupdbobject.hpp @@ -0,0 +1,31 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef USERGROUPDBOBJECT_H +#define USERGROUPDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "icinga/usergroup.hpp" +#include "base/configobject.hpp" + +namespace icinga +{ + +/** + * A UserGroup database object. + * + * @ingroup ido + */ +class UserGroupDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(UserGroupDbObject); + + UserGroupDbObject(const DbType::Ptr& type, const String& name1, const String& name2); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; +}; + +} + +#endif /* USERGROUPDBOBJECT_H */ diff --git a/lib/db_ido/zonedbobject.cpp b/lib/db_ido/zonedbobject.cpp new file mode 100644 index 0000000..b8ad0c1 --- /dev/null +++ b/lib/db_ido/zonedbobject.cpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "db_ido/zonedbobject.hpp" +#include "db_ido/dbtype.hpp" +#include "db_ido/dbvalue.hpp" +#include "base/logger.hpp" + +using namespace icinga; + + +REGISTER_DBTYPE(Zone, "zone", DbObjectTypeZone, "zone_object_id", ZoneDbObject); + +ZoneDbObject::ZoneDbObject(const DbType::Ptr& type, const String& name1, const String& name2) + : DbObject(type, name1, name2) +{ } + +Dictionary::Ptr ZoneDbObject::GetConfigFields() const +{ + Zone::Ptr zone = static_pointer_cast<Zone>(GetObject()); + + return new Dictionary({ + { "is_global", zone->IsGlobal() ? 1 : 0 }, + { "parent_zone_object_id", zone->GetParent() } + + }); +} + +Dictionary::Ptr ZoneDbObject::GetStatusFields() const +{ + Zone::Ptr zone = static_pointer_cast<Zone>(GetObject()); + + Log(LogDebug, "ZoneDbObject") + << "update status for zone '" << zone->GetName() << "'"; + + return new Dictionary({ + { "parent_zone_object_id", zone->GetParent() } + }); +} diff --git a/lib/db_ido/zonedbobject.hpp b/lib/db_ido/zonedbobject.hpp new file mode 100644 index 0000000..3901c81 --- /dev/null +++ b/lib/db_ido/zonedbobject.hpp @@ -0,0 +1,31 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef ZONEDBOBJECT_H +#define ZONEDBOBJECT_H + +#include "db_ido/dbobject.hpp" +#include "base/configobject.hpp" +#include "remote/zone.hpp" + +namespace icinga +{ + +/** + * An Endpoint database object. + * + * @ingroup ido + */ +class ZoneDbObject final : public DbObject +{ +public: + DECLARE_PTR_TYPEDEFS(ZoneDbObject); + + ZoneDbObject(const intrusive_ptr<DbType>& type, const String& name1, const String& name2); + + Dictionary::Ptr GetConfigFields() const override; + Dictionary::Ptr GetStatusFields() const override; +}; + +} + +#endif /* ZONEDBOBJECT_H */ |