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