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/thread/once.hpp>
30 using namespace icinga;
32 static int l_NextDowntimeID = 1;
33 static boost::mutex l_DowntimeMutex;
34 static std::map<int, String> l_LegacyDowntimesCache;
35 static Timer::Ptr l_DowntimesExpireTimer;
36 static Timer::Ptr l_DowntimesStartTimer;
38 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeAdded;
39 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeRemoved;
40 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeStarted;
41 boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeTriggered;
43 REGISTER_TYPE(Downtime);
45 String DowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
47 Downtime::Ptr downtime = dynamic_pointer_cast<Downtime>(context);
52 String name = downtime->GetHostName();
54 if (!downtime->GetServiceName().IsEmpty())
55 name += "!" + downtime->GetServiceName();
57 name += "!" + shortName;
62 Dictionary::Ptr DowntimeNameComposer::ParseName(const String& name) const
64 std::vector<String> tokens = name.Split("!");
66 if (tokens.size() < 2)
67 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Downtime name."));
69 Dictionary::Ptr result = new Dictionary();
70 result->Set("host_name", tokens[0]);
72 if (tokens.size() > 2) {
73 result->Set("service_name", tokens[1]);
74 result->Set("name", tokens[2]);
76 result->Set("name", tokens[1]);
82 void Downtime::OnAllConfigLoaded()
84 ObjectImpl<Downtime>::OnAllConfigLoaded();
86 Host::Ptr host = Host::GetByName(GetHostName());
88 if (GetServiceName().IsEmpty())
91 m_Checkable = host->GetServiceByShortName(GetServiceName());
94 BOOST_THROW_EXCEPTION(ScriptError("Downtime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
97 void Downtime::Start(bool runtimeCreated)
99 ObjectImpl<Downtime>::Start(runtimeCreated);
101 static boost::once_flag once = BOOST_ONCE_INIT;
103 boost::call_once(once, [this]() {
104 l_DowntimesStartTimer = new Timer();
105 l_DowntimesStartTimer->SetInterval(5);
106 l_DowntimesStartTimer->OnTimerExpired.connect(std::bind(&Downtime::DowntimesStartTimerHandler));
107 l_DowntimesStartTimer->Start();
109 l_DowntimesExpireTimer = new Timer();
110 l_DowntimesExpireTimer->SetInterval(60);
111 l_DowntimesExpireTimer->OnTimerExpired.connect(std::bind(&Downtime::DowntimesExpireTimerHandler));
112 l_DowntimesExpireTimer->Start();
116 boost::mutex::scoped_lock lock(l_DowntimeMutex);
118 SetLegacyId(l_NextDowntimeID);
119 l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
123 Checkable::Ptr checkable = GetCheckable();
125 checkable->RegisterDowntime(this);
128 OnDowntimeAdded(this);
130 /* if this object is already in a NOT-OK state trigger
131 * this downtime now *after* it has been added (important
134 if (!checkable->IsStateOK(checkable->GetStateRaw())) {
135 Log(LogNotice, "Downtime")
136 << "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
137 << " Triggering downtime now.";
142 void Downtime::Stop(bool runtimeRemoved)
144 GetCheckable()->UnregisterDowntime(this);
147 OnDowntimeRemoved(this);
149 ObjectImpl<Downtime>::Stop(runtimeRemoved);
152 Checkable::Ptr Downtime::GetCheckable() const
154 return static_pointer_cast<Checkable>(m_Checkable);
157 bool Downtime::IsInEffect() const
159 double now = Utility::GetTime();
161 if (now < GetStartTime() ||
168 double triggerTime = GetTriggerTime();
170 if (triggerTime == 0)
173 return (now < triggerTime + GetDuration());
176 bool Downtime::IsTriggered() const
178 double now = Utility::GetTime();
180 double triggerTime = GetTriggerTime();
182 return (triggerTime > 0 && triggerTime <= now);
185 bool Downtime::IsExpired() const
187 double now = Utility::GetTime();
190 return (GetEndTime() < now);
192 /* triggered flexible downtime not in effect anymore */
193 if (IsTriggered() && !IsInEffect())
195 /* flexible downtime never triggered */
196 else if (!IsTriggered() && (GetEndTime() < now))
203 bool Downtime::HasValidConfigOwner() const
205 String configOwner = GetConfigOwner();
206 return configOwner.IsEmpty() || GetObject<ScheduledDowntime>(configOwner);
209 int Downtime::GetNextDowntimeID()
211 boost::mutex::scoped_lock lock(l_DowntimeMutex);
213 return l_NextDowntimeID;
216 String Downtime::AddDowntime(const Checkable::Ptr& checkable, const String& author,
217 const String& comment, double startTime, double endTime, bool fixed,
218 const String& triggeredBy, double duration,
219 const String& scheduledDowntime, const String& scheduledBy,
220 const String& id, const MessageOrigin::Ptr& origin)
225 fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
229 Dictionary::Ptr attrs = new Dictionary();
231 attrs->Set("author", author);
232 attrs->Set("comment", comment);
233 attrs->Set("start_time", startTime);
234 attrs->Set("end_time", endTime);
235 attrs->Set("fixed", fixed);
236 attrs->Set("duration", duration);
237 attrs->Set("triggered_by", triggeredBy);
238 attrs->Set("scheduled_by", scheduledBy);
239 attrs->Set("config_owner", scheduledDowntime);
240 attrs->Set("entry_time", Utility::GetTime());
243 Service::Ptr service;
244 tie(host, service) = GetHostService(checkable);
246 attrs->Set("host_name", host->GetName());
248 attrs->Set("service_name", service->GetShortName());
250 String zone = checkable->GetZoneName();
253 attrs->Set("zone", zone);
255 String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, nullptr, attrs);
257 Array::Ptr errors = new Array();
259 if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors)) {
260 ObjectLock olock(errors);
261 for (const String& error : errors) {
262 Log(LogCritical, "Downtime", error);
265 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime."));
268 if (!triggeredBy.IsEmpty()) {
269 Downtime::Ptr parentDowntime = Downtime::GetByName(triggeredBy);
270 Array::Ptr triggers = parentDowntime->GetTriggers();
272 ObjectLock olock(triggers);
273 if (!triggers->Contains(fullName))
274 triggers->Add(fullName);
277 Downtime::Ptr downtime = Downtime::GetByName(fullName);
280 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime object."));
282 Log(LogNotice, "Downtime")
283 << "Added downtime '" << downtime->GetName()
284 << "' between '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", startTime)
285 << "' and '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endTime) << "'.";
290 void Downtime::RemoveDowntime(const String& id, bool cancelled, bool expired, const MessageOrigin::Ptr& origin)
292 Downtime::Ptr downtime = Downtime::GetByName(id);
294 if (!downtime || downtime->GetPackage() != "_api")
297 String config_owner = downtime->GetConfigOwner();
299 if (!config_owner.IsEmpty() && !expired) {
300 Log(LogWarning, "Downtime")
301 << "Cannot remove downtime '" << downtime->GetName() << "'. It is owned by scheduled downtime object '" << config_owner << "'";
305 downtime->SetWasCancelled(cancelled);
307 Log(LogNotice, "Downtime")
308 << "Removed downtime '" << downtime->GetName() << "' from object '" << downtime->GetCheckable()->GetName() << "'.";
310 Array::Ptr errors = new Array();
312 if (!ConfigObjectUtility::DeleteObject(downtime, false, errors)) {
313 ObjectLock olock(errors);
314 for (const String& error : errors) {
315 Log(LogCritical, "Downtime", error);
318 BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
322 bool Downtime::CanBeTriggered()
324 if (IsInEffect() && IsTriggered())
330 double now = Utility::GetTime();
332 if (now < GetStartTime() || now > GetEndTime())
338 void Downtime::TriggerDowntime()
340 if (!CanBeTriggered())
343 Log(LogNotice, "Downtime")
344 << "Triggering downtime '" << GetName() << "'.";
346 if (GetTriggerTime() == 0)
347 SetTriggerTime(Utility::GetTime());
349 Array::Ptr triggers = GetTriggers();
352 ObjectLock olock(triggers);
353 for (const String& triggerName : triggers) {
354 Downtime::Ptr downtime = Downtime::GetByName(triggerName);
359 downtime->TriggerDowntime();
363 OnDowntimeTriggered(this);
366 String Downtime::GetDowntimeIDFromLegacyID(int id)
368 boost::mutex::scoped_lock lock(l_DowntimeMutex);
370 auto it = l_LegacyDowntimesCache.find(id);
372 if (it == l_LegacyDowntimesCache.end())
378 void Downtime::DowntimesStartTimerHandler()
380 /* Start fixed downtimes. Flexible downtimes will be triggered on-demand. */
381 for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
382 if (downtime->IsActive() &&
383 downtime->CanBeTriggered() &&
384 downtime->GetFixed()) {
385 /* Send notifications. */
386 OnDowntimeStarted(downtime);
388 /* Trigger fixed downtime immediately. */
389 downtime->TriggerDowntime();
394 void Downtime::DowntimesExpireTimerHandler()
396 std::vector<Downtime::Ptr> downtimes;
398 for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
399 downtimes.push_back(downtime);
402 for (const Downtime::Ptr& downtime : downtimes) {
403 /* Only remove downtimes which are activated after daemon start. */
404 if (downtime->IsActive() && (downtime->IsExpired() || !downtime->HasValidConfigOwner()))
405 RemoveDowntime(downtime->GetName(), false, true);
409 void Downtime::ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
411 ObjectImpl<Downtime>::ValidateStartTime(lvalue, utils);
414 BOOST_THROW_EXCEPTION(ValidationError(this, { "start_time" }, "Start time must be greater than 0."));
417 void Downtime::ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
419 ObjectImpl<Downtime>::ValidateEndTime(lvalue, utils);
422 BOOST_THROW_EXCEPTION(ValidationError(this, { "end_time" }, "End time must be greater than 0."));