]> granicus.if.org Git - icinga2/blob - lib/icinga/downtime.cpp
Merge pull request #5964 from fedepires/fix/opentsdbwriter-host-tag-5963
[icinga2] / lib / icinga / downtime.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 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 "icinga/downtime.hpp"
21 #include "icinga/downtime-ti.cpp"
22 #include "icinga/host.hpp"
23 #include "icinga/scheduleddowntime.hpp"
24 #include "remote/configobjectutility.hpp"
25 #include "base/configtype.hpp"
26 #include "base/utility.hpp"
27 #include "base/timer.hpp"
28 #include <boost/algorithm/string/split.hpp>
29 #include <boost/algorithm/string/classification.hpp>
30 #include <boost/thread/once.hpp>
31
32 using namespace icinga;
33
34 static int l_NextDowntimeID = 1;
35 static boost::mutex l_DowntimeMutex;
36 static std::map<int, String> l_LegacyDowntimesCache;
37 static Timer::Ptr l_DowntimesExpireTimer;
38 static Timer::Ptr l_DowntimesStartTimer;
39
40 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeAdded;
41 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeRemoved;
42 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeStarted;
43 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeTriggered;
44
45 REGISTER_TYPE(Downtime);
46
47 String DowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
48 {
49         Downtime::Ptr downtime = dynamic_pointer_cast<Downtime>(context);
50
51         if (!downtime)
52                 return "";
53
54         String name = downtime->GetHostName();
55
56         if (!downtime->GetServiceName().IsEmpty())
57                 name += "!" + downtime->GetServiceName();
58
59         name += "!" + shortName;
60
61         return name;
62 }
63
64 Dictionary::Ptr DowntimeNameComposer::ParseName(const String& name) const
65 {
66         std::vector<String> tokens;
67         boost::algorithm::split(tokens, name, boost::is_any_of("!"));
68
69         if (tokens.size() < 2)
70                 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Downtime name."));
71
72         Dictionary::Ptr result = new Dictionary();
73         result->Set("host_name", tokens[0]);
74
75         if (tokens.size() > 2) {
76                 result->Set("service_name", tokens[1]);
77                 result->Set("name", tokens[2]);
78         } else {
79                 result->Set("name", tokens[1]);
80         }
81
82         return result;
83 }
84
85 void Downtime::OnAllConfigLoaded()
86 {
87         ObjectImpl<Downtime>::OnAllConfigLoaded();
88
89         Host::Ptr host = Host::GetByName(GetHostName());
90
91         if (GetServiceName().IsEmpty())
92                 m_Checkable = host;
93         else
94                 m_Checkable = host->GetServiceByShortName(GetServiceName());
95
96         if (!m_Checkable)
97                 BOOST_THROW_EXCEPTION(ScriptError("Downtime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
98 }
99
100 void Downtime::Start(bool runtimeCreated)
101 {
102         ObjectImpl<Downtime>::Start(runtimeCreated);
103
104         static boost::once_flag once = BOOST_ONCE_INIT;
105
106         boost::call_once(once, [this]() {
107                 l_DowntimesStartTimer = new Timer();
108                 l_DowntimesStartTimer->SetInterval(5);
109                 l_DowntimesStartTimer->OnTimerExpired.connect(std::bind(&Downtime::DowntimesStartTimerHandler));
110                 l_DowntimesStartTimer->Start();
111
112                 l_DowntimesExpireTimer = new Timer();
113                 l_DowntimesExpireTimer->SetInterval(60);
114                 l_DowntimesExpireTimer->OnTimerExpired.connect(std::bind(&Downtime::DowntimesExpireTimerHandler));
115                 l_DowntimesExpireTimer->Start();
116         });
117
118         {
119                 boost::mutex::scoped_lock lock(l_DowntimeMutex);
120
121                 SetLegacyId(l_NextDowntimeID);
122                 l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
123                 l_NextDowntimeID++;
124         }
125
126         Checkable::Ptr checkable = GetCheckable();
127
128         checkable->RegisterDowntime(this);
129
130         if (runtimeCreated)
131                 OnDowntimeAdded(this);
132
133         /* if this object is already in a NOT-OK state trigger
134          * this downtime now *after* it has been added (important
135          * for DB IDO, etc.)
136          */
137         if (!checkable->IsStateOK(checkable->GetStateRaw())) {
138                 Log(LogNotice, "Downtime")
139                         << "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
140                         << " Triggering downtime now.";
141                 TriggerDowntime();
142         }
143 }
144
145 void Downtime::Stop(bool runtimeRemoved)
146 {
147         GetCheckable()->UnregisterDowntime(this);
148
149         if (runtimeRemoved)
150                 OnDowntimeRemoved(this);
151
152         ObjectImpl<Downtime>::Stop(runtimeRemoved);
153 }
154
155 Checkable::Ptr Downtime::GetCheckable() const
156 {
157         return static_pointer_cast<Checkable>(m_Checkable);
158 }
159
160 bool Downtime::IsInEffect() const
161 {
162         double now = Utility::GetTime();
163
164         if (now < GetStartTime() ||
165                 now > GetEndTime())
166                 return false;
167
168         if (GetFixed())
169                 return true;
170
171         double triggerTime = GetTriggerTime();
172
173         if (triggerTime == 0)
174                 return false;
175
176         return (now < triggerTime + GetDuration());
177 }
178
179 bool Downtime::IsTriggered() const
180 {
181         double now = Utility::GetTime();
182
183         double triggerTime = GetTriggerTime();
184
185         return (triggerTime > 0 && triggerTime <= now);
186 }
187
188 bool Downtime::IsExpired() const
189 {
190         double now = Utility::GetTime();
191
192         if (GetFixed())
193                 return (GetEndTime() < now);
194         else {
195                 /* triggered flexible downtime not in effect anymore */
196                 if (IsTriggered() && !IsInEffect())
197                         return true;
198                 /* flexible downtime never triggered */
199                 else if (!IsTriggered() && (GetEndTime() < now))
200                         return true;
201                 else
202                         return false;
203         }
204 }
205
206 bool Downtime::HasValidConfigOwner() const
207 {
208         String configOwner = GetConfigOwner();
209         return configOwner.IsEmpty() || GetObject<ScheduledDowntime>(configOwner);
210 }
211
212 int Downtime::GetNextDowntimeID()
213 {
214         boost::mutex::scoped_lock lock(l_DowntimeMutex);
215
216         return l_NextDowntimeID;
217 }
218
219 String Downtime::AddDowntime(const Checkable::Ptr& checkable, const String& author,
220         const String& comment, double startTime, double endTime, bool fixed,
221         const String& triggeredBy, double duration,
222         const String& scheduledDowntime, const String& scheduledBy,
223         const String& id, const MessageOrigin::Ptr& origin)
224 {
225         String fullName;
226
227         if (id.IsEmpty())
228                 fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
229         else
230                 fullName = id;
231
232         Dictionary::Ptr attrs = new Dictionary();
233
234         attrs->Set("author", author);
235         attrs->Set("comment", comment);
236         attrs->Set("start_time", startTime);
237         attrs->Set("end_time", endTime);
238         attrs->Set("fixed", fixed);
239         attrs->Set("duration", duration);
240         attrs->Set("triggered_by", triggeredBy);
241         attrs->Set("scheduled_by", scheduledBy);
242         attrs->Set("config_owner", scheduledDowntime);
243         attrs->Set("entry_time", Utility::GetTime());
244
245         Host::Ptr host;
246         Service::Ptr service;
247         tie(host, service) = GetHostService(checkable);
248
249         attrs->Set("host_name", host->GetName());
250         if (service)
251                 attrs->Set("service_name", service->GetShortName());
252
253         String zone = checkable->GetZoneName();
254
255         if (!zone.IsEmpty())
256                 attrs->Set("zone", zone);
257
258         String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, nullptr, attrs);
259
260         Array::Ptr errors = new Array();
261
262         if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors)) {
263                 ObjectLock olock(errors);
264                 for (const String& error : errors) {
265                         Log(LogCritical, "Downtime", error);
266                 }
267
268                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime."));
269         }
270
271         if (!triggeredBy.IsEmpty()) {
272                 Downtime::Ptr parentDowntime = Downtime::GetByName(triggeredBy);
273                 Array::Ptr triggers = parentDowntime->GetTriggers();
274
275                 ObjectLock olock(triggers);
276                 if (!triggers->Contains(fullName))
277                         triggers->Add(fullName);
278         }
279
280         Downtime::Ptr downtime = Downtime::GetByName(fullName);
281
282         if (!downtime)
283                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime object."));
284
285         Log(LogNotice, "Downtime")
286                 << "Added downtime '" << downtime->GetName()
287                 << "' between '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", startTime)
288                 << "' and '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endTime) << "'.";
289
290         return fullName;
291 }
292
293 void Downtime::RemoveDowntime(const String& id, bool cancelled, bool expired, const MessageOrigin::Ptr& origin)
294 {
295         Downtime::Ptr downtime = Downtime::GetByName(id);
296
297         if (!downtime || downtime->GetPackage() != "_api")
298                 return;
299
300         String config_owner = downtime->GetConfigOwner();
301
302         if (!config_owner.IsEmpty() && !expired) {
303                 Log(LogWarning, "Downtime")
304                         << "Cannot remove downtime '" << downtime->GetName() << "'. It is owned by scheduled downtime object '" << config_owner << "'";
305                 return;
306         }
307
308         downtime->SetWasCancelled(cancelled);
309
310         Log(LogNotice, "Downtime")
311                 << "Removed downtime '" << downtime->GetName() << "' from object '" << downtime->GetCheckable()->GetName() << "'.";
312
313         Array::Ptr errors = new Array();
314
315         if (!ConfigObjectUtility::DeleteObject(downtime, false, errors)) {
316                 ObjectLock olock(errors);
317                 for (const String& error : errors) {
318                         Log(LogCritical, "Downtime", error);
319                 }
320
321                 BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
322         }
323 }
324
325 bool Downtime::CanBeTriggered()
326 {
327         if (IsInEffect() && IsTriggered())
328                 return false;
329
330         if (IsExpired())
331                 return false;
332
333         double now = Utility::GetTime();
334
335         if (now < GetStartTime() || now > GetEndTime())
336                 return false;
337
338         return true;
339 }
340
341 void Downtime::TriggerDowntime()
342 {
343         if (!CanBeTriggered())
344                 return;
345
346         Log(LogNotice, "Downtime")
347                 << "Triggering downtime '" << GetName() << "'.";
348
349         if (GetTriggerTime() == 0)
350                 SetTriggerTime(Utility::GetTime());
351
352         Array::Ptr triggers = GetTriggers();
353
354         {
355                 ObjectLock olock(triggers);
356                 for (const String& triggerName : triggers) {
357                         Downtime::Ptr downtime = Downtime::GetByName(triggerName);
358
359                         if (!downtime)
360                                 continue;
361
362                         downtime->TriggerDowntime();
363                 }
364         }
365
366         OnDowntimeTriggered(this);
367 }
368
369 String Downtime::GetDowntimeIDFromLegacyID(int id)
370 {
371         boost::mutex::scoped_lock lock(l_DowntimeMutex);
372
373         auto it = l_LegacyDowntimesCache.find(id);
374
375         if (it == l_LegacyDowntimesCache.end())
376                 return Empty;
377
378         return it->second;
379 }
380
381 void Downtime::DowntimesStartTimerHandler()
382 {
383         /* Start fixed downtimes. Flexible downtimes will be triggered on-demand. */
384         for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
385                 if (downtime->IsActive() &&
386                         downtime->CanBeTriggered() &&
387                         downtime->GetFixed()) {
388                         /* Send notifications. */
389                         OnDowntimeStarted(downtime);
390
391                         /* Trigger fixed downtime immediately. */
392                         downtime->TriggerDowntime();
393                 }
394         }
395 }
396
397 void Downtime::DowntimesExpireTimerHandler()
398 {
399         std::vector<Downtime::Ptr> downtimes;
400
401         for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
402                 downtimes.push_back(downtime);
403         }
404
405         for (const Downtime::Ptr& downtime : downtimes) {
406                 /* Only remove downtimes which are activated after daemon start. */
407                 if (downtime->IsActive() && (downtime->IsExpired() || !downtime->HasValidConfigOwner()))
408                         RemoveDowntime(downtime->GetName(), false, true);
409         }
410 }
411
412 void Downtime::ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
413 {
414         ObjectImpl<Downtime>::ValidateStartTime(lvalue, utils);
415
416         if (lvalue() <= 0)
417                 BOOST_THROW_EXCEPTION(ValidationError(this, { "start_time" }, "Start time must be greater than 0."));
418 }
419
420 void Downtime::ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
421 {
422         ObjectImpl<Downtime>::ValidateEndTime(lvalue, utils);
423
424         if (lvalue() <= 0)
425                 BOOST_THROW_EXCEPTION(ValidationError(this, { "end_time" }, "End time must be greater than 0."));
426 }