]> granicus.if.org Git - icinga2/blob - lib/db_ido/dbconnection.cpp
DB IDO: Do not deactivate objects during application reload/restart
[icinga2] / lib / db_ido / dbconnection.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "db_ido/dbconnection.hpp"
4 #include "db_ido/dbconnection-ti.cpp"
5 #include "db_ido/dbvalue.hpp"
6 #include "icinga/icingaapplication.hpp"
7 #include "icinga/host.hpp"
8 #include "icinga/service.hpp"
9 #include "base/configtype.hpp"
10 #include "base/convert.hpp"
11 #include "base/objectlock.hpp"
12 #include "base/utility.hpp"
13 #include "base/logger.hpp"
14 #include "base/exception.hpp"
15
16 using namespace icinga;
17
18 REGISTER_TYPE(DbConnection);
19
20 Timer::Ptr DbConnection::m_ProgramStatusTimer;
21 boost::once_flag DbConnection::m_OnceFlag = BOOST_ONCE_INIT;
22
23 void DbConnection::OnConfigLoaded()
24 {
25         ConfigObject::OnConfigLoaded();
26
27         Value categories = GetCategories();
28
29         SetCategoryFilter(FilterArrayToInt(categories, DbQuery::GetCategoryFilterMap(), DbCatEverything));
30
31         if (!GetEnableHa()) {
32                 Log(LogDebug, "DbConnection")
33                         << "HA functionality disabled. Won't pause IDO connection: " << GetName();
34
35                 SetHAMode(HARunEverywhere);
36         }
37
38         boost::call_once(m_OnceFlag, InitializeDbTimer);
39 }
40
41 void DbConnection::Start(bool runtimeCreated)
42 {
43         ObjectImpl<DbConnection>::Start(runtimeCreated);
44
45         Log(LogInformation, "DbConnection")
46                 << "'" << GetName() << "' started.";
47
48         DbObject::OnQuery.connect(std::bind(&DbConnection::ExecuteQuery, this, _1));
49         DbObject::OnMultipleQueries.connect(std::bind(&DbConnection::ExecuteMultipleQueries, this, _1));
50 }
51
52 void DbConnection::Stop(bool runtimeRemoved)
53 {
54         Log(LogInformation, "DbConnection")
55                 << "'" << GetName() << "' stopped.";
56
57         ObjectImpl<DbConnection>::Stop(runtimeRemoved);
58 }
59
60 void DbConnection::EnableActiveChangedHandler()
61 {
62         if (!m_ActiveChangedHandler) {
63                 ConfigObject::OnActiveChanged.connect(std::bind(&DbConnection::UpdateObject, this, _1));
64                 m_ActiveChangedHandler = true;
65         }
66 }
67
68 void DbConnection::Resume()
69 {
70         ConfigObject::Resume();
71
72         Log(LogInformation, "DbConnection")
73                 << "Resuming IDO connection: " << GetName();
74
75         m_CleanUpTimer = new Timer();
76         m_CleanUpTimer->SetInterval(60);
77         m_CleanUpTimer->OnTimerExpired.connect(std::bind(&DbConnection::CleanUpHandler, this));
78         m_CleanUpTimer->Start();
79 }
80
81 void DbConnection::Pause()
82 {
83         ConfigObject::Pause();
84
85         Log(LogInformation, "DbConnection")
86                 << "Pausing IDO connection: " << GetName();
87
88         m_CleanUpTimer.reset();
89
90         DbQuery query1;
91         query1.Table = "programstatus";
92         query1.IdColumn = "programstatus_id";
93         query1.Type = DbQueryUpdate;
94         query1.Category = DbCatProgramStatus;
95         query1.WhereCriteria = new Dictionary({
96                 { "instance_id", 0 }  /* DbConnection class fills in real ID */
97         });
98
99         query1.Fields = new Dictionary({
100                 { "instance_id", 0 }, /* DbConnection class fills in real ID */
101                 { "program_end_time", DbValue::FromTimestamp(Utility::GetTime()) }
102         });
103
104         query1.Priority = PriorityHigh;
105
106         ExecuteQuery(query1);
107
108         NewTransaction();
109 }
110
111 void DbConnection::InitializeDbTimer()
112 {
113         m_ProgramStatusTimer = new Timer();
114         m_ProgramStatusTimer->SetInterval(10);
115         m_ProgramStatusTimer->OnTimerExpired.connect(std::bind(&DbConnection::UpdateProgramStatus));
116         m_ProgramStatusTimer->Start();
117 }
118
119 void DbConnection::InsertRuntimeVariable(const String& key, const Value& value)
120 {
121         DbQuery query;
122         query.Table = "runtimevariables";
123         query.Type = DbQueryInsert;
124         query.Category = DbCatProgramStatus;
125         query.Fields = new Dictionary({
126                 { "instance_id", 0 }, /* DbConnection class fills in real ID */
127                 { "varname", key },
128                 { "varvalue", value }
129         });
130         DbObject::OnQuery(query);
131 }
132
133 void DbConnection::UpdateProgramStatus()
134 {
135         Log(LogNotice, "DbConnection")
136                 << "Updating programstatus table.";
137
138         std::vector<DbQuery> queries;
139
140         DbQuery query1;
141         query1.Table = "programstatus";
142         query1.IdColumn = "programstatus_id";
143         query1.Type = DbQueryInsert | DbQueryUpdate;
144         query1.Category = DbCatProgramStatus;
145
146         query1.Fields = new Dictionary({
147                 { "instance_id", 0 }, /* DbConnection class fills in real ID */
148                 { "program_version", Application::GetAppVersion() },
149                 { "status_update_time", DbValue::FromTimestamp(Utility::GetTime()) },
150                 { "program_start_time", DbValue::FromTimestamp(Application::GetStartTime()) },
151                 { "is_currently_running", 1 },
152                 { "endpoint_name", IcingaApplication::GetInstance()->GetNodeName() },
153                 { "process_id", Utility::GetPid() },
154                 { "daemon_mode", 1 },
155                 { "last_command_check", DbValue::FromTimestamp(Utility::GetTime()) },
156                 { "notifications_enabled", (IcingaApplication::GetInstance()->GetEnableNotifications() ? 1 : 0) },
157                 { "active_host_checks_enabled", (IcingaApplication::GetInstance()->GetEnableHostChecks() ? 1 : 0) },
158                 { "passive_host_checks_enabled", 1 },
159                 { "active_service_checks_enabled", (IcingaApplication::GetInstance()->GetEnableServiceChecks() ? 1 : 0) },
160                 { "passive_service_checks_enabled", 1 },
161                 { "event_handlers_enabled", (IcingaApplication::GetInstance()->GetEnableEventHandlers() ? 1 : 0) },
162                 { "flap_detection_enabled", (IcingaApplication::GetInstance()->GetEnableFlapping() ? 1 : 0) },
163                 { "process_performance_data", (IcingaApplication::GetInstance()->GetEnablePerfdata() ? 1 : 0) }
164         });
165
166         query1.WhereCriteria = new Dictionary({
167                 { "instance_id", 0 }  /* DbConnection class fills in real ID */
168         });
169
170         query1.Priority = PriorityHigh;
171         queries.emplace_back(std::move(query1));
172
173         DbQuery query2;
174         query2.Type = DbQueryNewTransaction;
175         queries.emplace_back(std::move(query2));
176
177         DbObject::OnMultipleQueries(queries);
178
179         DbQuery query3;
180         query3.Table = "runtimevariables";
181         query3.Type = DbQueryDelete;
182         query3.Category = DbCatProgramStatus;
183         query3.WhereCriteria = new Dictionary({
184                 { "instance_id", 0 } /* DbConnection class fills in real ID */
185         });
186         DbObject::OnQuery(query3);
187
188         InsertRuntimeVariable("total_services", ConfigType::Get<Service>()->GetObjectCount());
189         InsertRuntimeVariable("total_scheduled_services", ConfigType::Get<Service>()->GetObjectCount());
190         InsertRuntimeVariable("total_hosts", ConfigType::Get<Host>()->GetObjectCount());
191         InsertRuntimeVariable("total_scheduled_hosts", ConfigType::Get<Host>()->GetObjectCount());
192 }
193
194 void DbConnection::CleanUpHandler()
195 {
196         auto now = static_cast<long>(Utility::GetTime());
197
198         struct {
199                 String name;
200                 String time_column;
201         } tables[] = {
202                 { "acknowledgements", "entry_time" },
203                 { "commenthistory", "entry_time" },
204                 { "contactnotifications", "start_time" },
205                 { "contactnotificationmethods", "start_time" },
206                 { "downtimehistory", "entry_time" },
207                 { "eventhandlers", "start_time" },
208                 { "externalcommands", "entry_time" },
209                 { "flappinghistory", "event_time" },
210                 { "hostchecks", "start_time" },
211                 { "logentries", "logentry_time" },
212                 { "notifications", "start_time" },
213                 { "processevents", "event_time" },
214                 { "statehistory", "state_time" },
215                 { "servicechecks", "start_time" },
216                 { "systemcommands", "start_time" }
217         };
218
219         for (auto& table : tables) {
220                 double max_age = GetCleanup()->Get(table.name + "_age");
221
222                 if (max_age == 0)
223                         continue;
224
225                 CleanUpExecuteQuery(table.name, table.time_column, now - max_age);
226                 Log(LogNotice, "DbConnection")
227                         << "Cleanup (" << table.name << "): " << max_age
228                         << " now: " << now
229                         << " old: " << now - max_age;
230         }
231
232 }
233
234 void DbConnection::CleanUpExecuteQuery(const String&, const String&, double)
235 {
236         /* Default handler does nothing. */
237 }
238
239 void DbConnection::SetConfigHash(const DbObject::Ptr& dbobj, const String& hash)
240 {
241         SetConfigHash(dbobj->GetType(), GetObjectID(dbobj), hash);
242 }
243
244 void DbConnection::SetConfigHash(const DbType::Ptr& type, const DbReference& objid, const String& hash)
245 {
246         if (!objid.IsValid())
247                 return;
248
249         if (!hash.IsEmpty())
250                 m_ConfigHashes[std::make_pair(type, objid)] = hash;
251         else
252                 m_ConfigHashes.erase(std::make_pair(type, objid));
253 }
254
255 String DbConnection::GetConfigHash(const DbObject::Ptr& dbobj) const
256 {
257         return GetConfigHash(dbobj->GetType(), GetObjectID(dbobj));
258 }
259
260 String DbConnection::GetConfigHash(const DbType::Ptr& type, const DbReference& objid) const
261 {
262         if (!objid.IsValid())
263                 return String();
264
265         auto it = m_ConfigHashes.find(std::make_pair(type, objid));
266
267         if (it == m_ConfigHashes.end())
268                 return String();
269
270         return it->second;
271 }
272
273 void DbConnection::SetObjectID(const DbObject::Ptr& dbobj, const DbReference& dbref)
274 {
275         if (dbref.IsValid())
276                 m_ObjectIDs[dbobj] = dbref;
277         else
278                 m_ObjectIDs.erase(dbobj);
279 }
280
281 DbReference DbConnection::GetObjectID(const DbObject::Ptr& dbobj) const
282 {
283         auto it = m_ObjectIDs.find(dbobj);
284
285         if (it == m_ObjectIDs.end())
286                 return {};
287
288         return it->second;
289 }
290
291 void DbConnection::SetInsertID(const DbObject::Ptr& dbobj, const DbReference& dbref)
292 {
293         SetInsertID(dbobj->GetType(), GetObjectID(dbobj), dbref);
294 }
295
296 void DbConnection::SetInsertID(const DbType::Ptr& type, const DbReference& objid, const DbReference& dbref)
297 {
298         if (!objid.IsValid())
299                 return;
300
301         if (dbref.IsValid())
302                 m_InsertIDs[std::make_pair(type, objid)] = dbref;
303         else
304                 m_InsertIDs.erase(std::make_pair(type, objid));
305 }
306
307 DbReference DbConnection::GetInsertID(const DbObject::Ptr& dbobj) const
308 {
309         return GetInsertID(dbobj->GetType(), GetObjectID(dbobj));
310 }
311
312 DbReference DbConnection::GetInsertID(const DbType::Ptr& type, const DbReference& objid) const
313 {
314         if (!objid.IsValid())
315                 return {};
316
317         auto it = m_InsertIDs.find(std::make_pair(type, objid));
318
319         if (it == m_InsertIDs.end())
320                 return DbReference();
321
322         return it->second;
323 }
324
325 void DbConnection::SetObjectActive(const DbObject::Ptr& dbobj, bool active)
326 {
327         if (active)
328                 m_ActiveObjects.insert(dbobj);
329         else
330                 m_ActiveObjects.erase(dbobj);
331 }
332
333 bool DbConnection::GetObjectActive(const DbObject::Ptr& dbobj) const
334 {
335         return (m_ActiveObjects.find(dbobj) != m_ActiveObjects.end());
336 }
337
338 void DbConnection::ClearIDCache()
339 {
340         SetIDCacheValid(false);
341
342         m_ObjectIDs.clear();
343         m_InsertIDs.clear();
344         m_ActiveObjects.clear();
345         m_ConfigUpdates.clear();
346         m_StatusUpdates.clear();
347         m_ConfigHashes.clear();
348 }
349
350 void DbConnection::SetConfigUpdate(const DbObject::Ptr& dbobj, bool hasupdate)
351 {
352         if (hasupdate)
353                 m_ConfigUpdates.insert(dbobj);
354         else
355                 m_ConfigUpdates.erase(dbobj);
356 }
357
358 bool DbConnection::GetConfigUpdate(const DbObject::Ptr& dbobj) const
359 {
360         return (m_ConfigUpdates.find(dbobj) != m_ConfigUpdates.end());
361 }
362
363 void DbConnection::SetStatusUpdate(const DbObject::Ptr& dbobj, bool hasupdate)
364 {
365         if (hasupdate)
366                 m_StatusUpdates.insert(dbobj);
367         else
368                 m_StatusUpdates.erase(dbobj);
369 }
370
371 bool DbConnection::GetStatusUpdate(const DbObject::Ptr& dbobj) const
372 {
373         return (m_StatusUpdates.find(dbobj) != m_StatusUpdates.end());
374 }
375
376 void DbConnection::UpdateObject(const ConfigObject::Ptr& object)
377 {
378         bool isShuttingDown = Application::IsShuttingDown();
379         bool isRestarting = Application::IsRestarting();
380
381 #ifdef I2_DEBUG
382         if (isShuttingDown || isRestarting) {
383                 //Log(LogDebug, "DbConnection")
384                 //      << "Updating object '" << object->GetName() << "' \t\t active '" << Convert::ToLong(object->IsActive())
385                 //      << "' shutting down '" << Convert::ToLong(isShuttingDown) << "' restarting '" << Convert::ToLong(isRestarting) << "'.";
386         }
387 #endif /* I2_DEBUG */
388
389         /* Wait until a database connection is established on reconnect. */
390         if (!GetConnected())
391                 return;
392
393         /* Don't update inactive objects during shutdown/reload/restart.
394          * They would be marked as deleted. This gets triggered with ConfigObject::StopObjects().
395          * During startup/reconnect this is fine, the handler is not active there.
396          */
397         if (isShuttingDown || isRestarting)
398                 return;
399
400         DbObject::Ptr dbobj = DbObject::GetOrCreateByObject(object);
401
402         if (dbobj) {
403                 bool dbActive = GetObjectActive(dbobj);
404                 bool active = object->IsActive();
405
406                 if (active) {
407                         if (!dbActive)
408                                 ActivateObject(dbobj);
409
410                         Dictionary::Ptr configFields = dbobj->GetConfigFields();
411                         String configHash = dbobj->CalculateConfigHash(configFields);
412                         ASSERT(configHash.GetLength() <= 64);
413                         configFields->Set("config_hash", configHash);
414
415                         String cachedHash = GetConfigHash(dbobj);
416
417                         if (cachedHash != configHash) {
418                                 dbobj->SendConfigUpdateHeavy(configFields);
419                                 dbobj->SendStatusUpdate();
420                         } else {
421                                 dbobj->SendConfigUpdateLight();
422                         }
423                 } else if (!active) {
424                         /* This may happen on reload/restart actions too
425                          * and is blocked above already.
426                          *
427                          * Deactivate the deleted object no matter
428                          * which state it had in the database.
429                          */
430                         DeactivateObject(dbobj);
431                 }
432         }
433 }
434
435 void DbConnection::UpdateAllObjects()
436 {
437         for (const Type::Ptr& type : Type::GetAllTypes()) {
438                 auto *dtype = dynamic_cast<ConfigType *>(type.get());
439
440                 if (!dtype)
441                         continue;
442
443                 for (const ConfigObject::Ptr& object : dtype->GetObjects()) {
444                         UpdateObject(object);
445                 }
446         }
447 }
448
449 void DbConnection::PrepareDatabase()
450 {
451         for (const DbType::Ptr& type : DbType::GetAllTypes()) {
452                 FillIDCache(type);
453         }
454 }
455
456 void DbConnection::ValidateFailoverTimeout(const Lazy<double>& lvalue, const ValidationUtils& utils)
457 {
458         ObjectImpl<DbConnection>::ValidateFailoverTimeout(lvalue, utils);
459
460         if (lvalue() < 30)
461                 BOOST_THROW_EXCEPTION(ValidationError(this, { "failover_timeout" }, "Failover timeout minimum is 30s."));
462 }
463
464 void DbConnection::ValidateCategories(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
465 {
466         ObjectImpl<DbConnection>::ValidateCategories(lvalue, utils);
467
468         int filter = FilterArrayToInt(lvalue(), DbQuery::GetCategoryFilterMap(), 0);
469
470         if (filter != DbCatEverything && (filter & ~(DbCatInvalid | DbCatEverything | DbCatConfig | DbCatState |
471                 DbCatAcknowledgement | DbCatComment | DbCatDowntime | DbCatEventHandler | DbCatExternalCommand |
472                 DbCatFlapping | DbCatLog | DbCatNotification | DbCatProgramStatus | DbCatRetention |
473                 DbCatStateHistory)) != 0)
474                 BOOST_THROW_EXCEPTION(ValidationError(this, { "categories" }, "categories filter is invalid."));
475 }
476
477 void DbConnection::IncreaseQueryCount()
478 {
479         double now = Utility::GetTime();
480
481         boost::mutex::scoped_lock lock(m_StatsMutex);
482         m_QueryStats.InsertValue(now, 1);
483 }
484
485 int DbConnection::GetQueryCount(RingBuffer::SizeType span)
486 {
487         boost::mutex::scoped_lock lock(m_StatsMutex);
488         return m_QueryStats.UpdateAndGetValues(Utility::GetTime(), span);
489 }
490
491 bool DbConnection::IsIDCacheValid() const
492 {
493         return m_IDCacheValid;
494 }
495
496 void DbConnection::SetIDCacheValid(bool valid)
497 {
498         m_IDCacheValid = valid;
499 }
500
501 int DbConnection::GetSessionToken()
502 {
503         return Application::GetStartTime();
504 }