mirror of
https://github.com/Icinga/icinga2.git
synced 2025-09-18 15:27:47 +02:00
This follows the same principle as with the shutdown handler, and was introduced with the changed reload handling with 2.9. Previously IsShuttingDown() was sufficient which got set at one location. SigUsr2 as handler introduced a new location where m_ShuttingDown is not necessarily set yet. Since this handler gets called when l_Restarting is enabled, we'll use this flag to avoid config update events resulting in object deactivation (object->IsActive() always returns false). refs #5996 refs #6691 refs #6970 fixes #7125
505 lines
14 KiB
C++
505 lines
14 KiB
C++
/* 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.";
|
|
|
|
DbObject::OnQuery.connect(std::bind(&DbConnection::ExecuteQuery, this, _1));
|
|
DbObject::OnMultipleQueries.connect(std::bind(&DbConnection::ExecuteMultipleQueries, this, _1));
|
|
}
|
|
|
|
void DbConnection::Stop(bool runtimeRemoved)
|
|
{
|
|
Log(LogInformation, "DbConnection")
|
|
<< "'" << GetName() << "' stopped.";
|
|
|
|
ObjectImpl<DbConnection>::Stop(runtimeRemoved);
|
|
}
|
|
|
|
void DbConnection::EnableActiveChangedHandler()
|
|
{
|
|
if (!m_ActiveChangedHandler) {
|
|
ConfigObject::OnActiveChanged.connect(std::bind(&DbConnection::UpdateObject, this, _1));
|
|
m_ActiveChangedHandler = true;
|
|
}
|
|
}
|
|
|
|
void DbConnection::Resume()
|
|
{
|
|
ConfigObject::Resume();
|
|
|
|
Log(LogInformation, "DbConnection")
|
|
<< "Resuming IDO connection: " << GetName();
|
|
|
|
m_CleanUpTimer = new Timer();
|
|
m_CleanUpTimer->SetInterval(60);
|
|
m_CleanUpTimer->OnTimerExpired.connect(std::bind(&DbConnection::CleanUpHandler, this));
|
|
m_CleanUpTimer->Start();
|
|
}
|
|
|
|
void DbConnection::Pause()
|
|
{
|
|
ConfigObject::Pause();
|
|
|
|
Log(LogInformation, "DbConnection")
|
|
<< "Pausing IDO connection: " << GetName();
|
|
|
|
m_CleanUpTimer.reset();
|
|
|
|
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()) }
|
|
});
|
|
|
|
query1.Priority = PriorityHigh;
|
|
|
|
ExecuteQuery(query1);
|
|
|
|
NewTransaction();
|
|
}
|
|
|
|
void DbConnection::InitializeDbTimer()
|
|
{
|
|
m_ProgramStatusTimer = new Timer();
|
|
m_ProgramStatusTimer->SetInterval(10);
|
|
m_ProgramStatusTimer->OnTimerExpired.connect(std::bind(&DbConnection::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()
|
|
{
|
|
Log(LogNotice, "DbConnection")
|
|
<< "Updating programstatus table.";
|
|
|
|
std::vector<DbQuery> queries;
|
|
|
|
DbQuery query1;
|
|
query1.Table = "programstatus";
|
|
query1.IdColumn = "programstatus_id";
|
|
query1.Type = DbQueryInsert | DbQueryUpdate;
|
|
query1.Category = DbCatProgramStatus;
|
|
|
|
query1.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::GetInstance()->GetNodeName() },
|
|
{ "process_id", Utility::GetPid() },
|
|
{ "daemon_mode", 1 },
|
|
{ "last_command_check", DbValue::FromTimestamp(Utility::GetTime()) },
|
|
{ "notifications_enabled", (IcingaApplication::GetInstance()->GetEnableNotifications() ? 1 : 0) },
|
|
{ "active_host_checks_enabled", (IcingaApplication::GetInstance()->GetEnableHostChecks() ? 1 : 0) },
|
|
{ "passive_host_checks_enabled", 1 },
|
|
{ "active_service_checks_enabled", (IcingaApplication::GetInstance()->GetEnableServiceChecks() ? 1 : 0) },
|
|
{ "passive_service_checks_enabled", 1 },
|
|
{ "event_handlers_enabled", (IcingaApplication::GetInstance()->GetEnableEventHandlers() ? 1 : 0) },
|
|
{ "flap_detection_enabled", (IcingaApplication::GetInstance()->GetEnableFlapping() ? 1 : 0) },
|
|
{ "process_performance_data", (IcingaApplication::GetInstance()->GetEnablePerfdata() ? 1 : 0) }
|
|
});
|
|
|
|
query1.WhereCriteria = new Dictionary({
|
|
{ "instance_id", 0 } /* DbConnection class fills in real ID */
|
|
});
|
|
|
|
query1.Priority = PriorityHigh;
|
|
queries.emplace_back(std::move(query1));
|
|
|
|
DbQuery query2;
|
|
query2.Type = DbQueryNewTransaction;
|
|
queries.emplace_back(std::move(query2));
|
|
|
|
DbObject::OnMultipleQueries(queries);
|
|
|
|
DbQuery query3;
|
|
query3.Table = "runtimevariables";
|
|
query3.Type = DbQueryDelete;
|
|
query3.Category = DbCatProgramStatus;
|
|
query3.WhereCriteria = new Dictionary({
|
|
{ "instance_id", 0 } /* DbConnection class fills in real ID */
|
|
});
|
|
DbObject::OnQuery(query3);
|
|
|
|
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::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()) {
|
|
UpdateObject(object);
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
boost::mutex::scoped_lock lock(m_StatsMutex);
|
|
m_QueryStats.InsertValue(now, 1);
|
|
}
|
|
|
|
int DbConnection::GetQueryCount(RingBuffer::SizeType span)
|
|
{
|
|
boost::mutex::scoped_lock 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();
|
|
}
|