1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
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. *
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. *
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 ******************************************************************************/
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>
32 using namespace icinga;
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;
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;
45 REGISTER_TYPE(Downtime);
47 String DowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
49 Downtime::Ptr downtime = dynamic_pointer_cast<Downtime>(context);
54 String name = downtime->GetHostName();
56 if (!downtime->GetServiceName().IsEmpty())
57 name += "!" + downtime->GetServiceName();
59 name += "!" + shortName;
64 Dictionary::Ptr DowntimeNameComposer::ParseName(const String& name) const
66 std::vector<String> tokens;
67 boost::algorithm::split(tokens, name, boost::is_any_of("!"));
69 if (tokens.size() < 2)
70 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Downtime name."));
72 Dictionary::Ptr result = new Dictionary();
73 result->Set("host_name", tokens[0]);
75 if (tokens.size() > 2) {
76 result->Set("service_name", tokens[1]);
77 result->Set("name", tokens[2]);
79 result->Set("name", tokens[1]);
85 void Downtime::OnAllConfigLoaded()
87 ObjectImpl<Downtime>::OnAllConfigLoaded();
89 Host::Ptr host = Host::GetByName(GetHostName());
91 if (GetServiceName().IsEmpty())
94 m_Checkable = host->GetServiceByShortName(GetServiceName());
97 BOOST_THROW_EXCEPTION(ScriptError("Downtime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
100 void Downtime::Start(bool runtimeCreated)
102 ObjectImpl<Downtime>::Start(runtimeCreated);
104 static boost::once_flag once = BOOST_ONCE_INIT;
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();
112 l_DowntimesExpireTimer = new Timer();
113 l_DowntimesExpireTimer->SetInterval(60);
114 l_DowntimesExpireTimer->OnTimerExpired.connect(std::bind(&Downtime::DowntimesExpireTimerHandler));
115 l_DowntimesExpireTimer->Start();
119 boost::mutex::scoped_lock lock(l_DowntimeMutex);
121 SetLegacyId(l_NextDowntimeID);
122 l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
126 Checkable::Ptr checkable = GetCheckable();
128 checkable->RegisterDowntime(this);
131 OnDowntimeAdded(this);
133 /* if this object is already in a NOT-OK state trigger
134 * this downtime now *after* it has been added (important
137 if (!checkable->IsStateOK(checkable->GetStateRaw())) {
138 Log(LogNotice, "Downtime")
139 << "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
140 << " Triggering downtime now.";
145 void Downtime::Stop(bool runtimeRemoved)
147 GetCheckable()->UnregisterDowntime(this);
150 OnDowntimeRemoved(this);
152 ObjectImpl<Downtime>::Stop(runtimeRemoved);
155 Checkable::Ptr Downtime::GetCheckable() const
157 return static_pointer_cast<Checkable>(m_Checkable);
160 bool Downtime::IsInEffect() const
162 double now = Utility::GetTime();
164 if (now < GetStartTime() ||
171 double triggerTime = GetTriggerTime();
173 if (triggerTime == 0)
176 return (now < triggerTime + GetDuration());
179 bool Downtime::IsTriggered() const
181 double now = Utility::GetTime();
183 double triggerTime = GetTriggerTime();
185 return (triggerTime > 0 && triggerTime <= now);
188 bool Downtime::IsExpired() const
190 double now = Utility::GetTime();
193 return (GetEndTime() < now);
195 /* triggered flexible downtime not in effect anymore */
196 if (IsTriggered() && !IsInEffect())
198 /* flexible downtime never triggered */
199 else if (!IsTriggered() && (GetEndTime() < now))
206 bool Downtime::HasValidConfigOwner() const
208 String configOwner = GetConfigOwner();
209 return configOwner.IsEmpty() || GetObject<ScheduledDowntime>(configOwner);
212 int Downtime::GetNextDowntimeID()
214 boost::mutex::scoped_lock lock(l_DowntimeMutex);
216 return l_NextDowntimeID;
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)
228 fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
232 Dictionary::Ptr attrs = new Dictionary();
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());
246 Service::Ptr service;
247 tie(host, service) = GetHostService(checkable);
249 attrs->Set("host_name", host->GetName());
251 attrs->Set("service_name", service->GetShortName());
253 String zone = checkable->GetZoneName();
256 attrs->Set("zone", zone);
258 String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, nullptr, attrs);
260 Array::Ptr errors = new Array();
262 if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors)) {
263 ObjectLock olock(errors);
264 for (const String& error : errors) {
265 Log(LogCritical, "Downtime", error);
268 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime."));
271 if (!triggeredBy.IsEmpty()) {
272 Downtime::Ptr parentDowntime = Downtime::GetByName(triggeredBy);
273 Array::Ptr triggers = parentDowntime->GetTriggers();
275 ObjectLock olock(triggers);
276 if (!triggers->Contains(fullName))
277 triggers->Add(fullName);
280 Downtime::Ptr downtime = Downtime::GetByName(fullName);
283 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime object."));
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) << "'.";
293 void Downtime::RemoveDowntime(const String& id, bool cancelled, bool expired, const MessageOrigin::Ptr& origin)
295 Downtime::Ptr downtime = Downtime::GetByName(id);
297 if (!downtime || downtime->GetPackage() != "_api")
300 String config_owner = downtime->GetConfigOwner();
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 << "'";
308 downtime->SetWasCancelled(cancelled);
310 Log(LogNotice, "Downtime")
311 << "Removed downtime '" << downtime->GetName() << "' from object '" << downtime->GetCheckable()->GetName() << "'.";
313 Array::Ptr errors = new Array();
315 if (!ConfigObjectUtility::DeleteObject(downtime, false, errors)) {
316 ObjectLock olock(errors);
317 for (const String& error : errors) {
318 Log(LogCritical, "Downtime", error);
321 BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
325 bool Downtime::CanBeTriggered()
327 if (IsInEffect() && IsTriggered())
333 double now = Utility::GetTime();
335 if (now < GetStartTime() || now > GetEndTime())
341 void Downtime::TriggerDowntime()
343 if (!CanBeTriggered())
346 Log(LogNotice, "Downtime")
347 << "Triggering downtime '" << GetName() << "'.";
349 if (GetTriggerTime() == 0)
350 SetTriggerTime(Utility::GetTime());
352 Array::Ptr triggers = GetTriggers();
355 ObjectLock olock(triggers);
356 for (const String& triggerName : triggers) {
357 Downtime::Ptr downtime = Downtime::GetByName(triggerName);
362 downtime->TriggerDowntime();
366 OnDowntimeTriggered(this);
369 String Downtime::GetDowntimeIDFromLegacyID(int id)
371 boost::mutex::scoped_lock lock(l_DowntimeMutex);
373 auto it = l_LegacyDowntimesCache.find(id);
375 if (it == l_LegacyDowntimesCache.end())
381 void Downtime::DowntimesStartTimerHandler()
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);
391 /* Trigger fixed downtime immediately. */
392 downtime->TriggerDowntime();
397 void Downtime::DowntimesExpireTimerHandler()
399 std::vector<Downtime::Ptr> downtimes;
401 for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
402 downtimes.push_back(downtime);
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);
412 void Downtime::ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
414 ObjectImpl<Downtime>::ValidateStartTime(lvalue, utils);
417 BOOST_THROW_EXCEPTION(ValidationError(this, { "start_time" }, "Start time must be greater than 0."));
420 void Downtime::ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
422 ObjectImpl<Downtime>::ValidateEndTime(lvalue, utils);
425 BOOST_THROW_EXCEPTION(ValidationError(this, { "end_time" }, "End time must be greater than 0."));