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