1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
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>
13 using namespace icinga;
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;
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;
26 REGISTER_TYPE(Downtime);
28 INITIALIZE_ONCE(&Downtime::StaticInitialize);
30 void Downtime::StaticInitialize()
32 ScriptGlobal::Set("Icinga.DowntimeNoChildren", "DowntimeNoChildren", true);
33 ScriptGlobal::Set("Icinga.DowntimeTriggeredChildren", "DowntimeTriggeredChildren", true);
34 ScriptGlobal::Set("Icinga.DowntimeNonTriggeredChildren", "DowntimeNonTriggeredChildren", true);
37 String DowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
39 Downtime::Ptr downtime = dynamic_pointer_cast<Downtime>(context);
44 String name = downtime->GetHostName();
46 if (!downtime->GetServiceName().IsEmpty())
47 name += "!" + downtime->GetServiceName();
49 name += "!" + shortName;
54 Dictionary::Ptr DowntimeNameComposer::ParseName(const String& name) const
56 std::vector<String> tokens = name.Split("!");
58 if (tokens.size() < 2)
59 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Downtime name."));
61 Dictionary::Ptr result = new Dictionary();
62 result->Set("host_name", tokens[0]);
64 if (tokens.size() > 2) {
65 result->Set("service_name", tokens[1]);
66 result->Set("name", tokens[2]);
68 result->Set("name", tokens[1]);
74 void Downtime::OnAllConfigLoaded()
76 ObjectImpl<Downtime>::OnAllConfigLoaded();
78 Host::Ptr host = Host::GetByName(GetHostName());
80 if (GetServiceName().IsEmpty())
83 m_Checkable = host->GetServiceByShortName(GetServiceName());
86 BOOST_THROW_EXCEPTION(ScriptError("Downtime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
89 void Downtime::Start(bool runtimeCreated)
91 ObjectImpl<Downtime>::Start(runtimeCreated);
93 static boost::once_flag once = BOOST_ONCE_INIT;
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();
101 l_DowntimesExpireTimer = new Timer();
102 l_DowntimesExpireTimer->SetInterval(60);
103 l_DowntimesExpireTimer->OnTimerExpired.connect(std::bind(&Downtime::DowntimesExpireTimerHandler));
104 l_DowntimesExpireTimer->Start();
108 boost::mutex::scoped_lock lock(l_DowntimeMutex);
110 SetLegacyId(l_NextDowntimeID);
111 l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
115 Checkable::Ptr checkable = GetCheckable();
117 checkable->RegisterDowntime(this);
120 OnDowntimeAdded(this);
122 /* if this object is already in a NOT-OK state trigger
123 * this downtime now *after* it has been added (important
126 if (!checkable->IsStateOK(checkable->GetStateRaw())) {
127 Log(LogNotice, "Downtime")
128 << "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
129 << " Triggering downtime now.";
134 void Downtime::Stop(bool runtimeRemoved)
136 GetCheckable()->UnregisterDowntime(this);
139 OnDowntimeRemoved(this);
141 ObjectImpl<Downtime>::Stop(runtimeRemoved);
144 Checkable::Ptr Downtime::GetCheckable() const
146 return static_pointer_cast<Checkable>(m_Checkable);
149 bool Downtime::IsInEffect() const
151 double now = Utility::GetTime();
154 /* fixed downtimes are in effect during the entire [start..end) interval */
155 return (now >= GetStartTime() && now < GetEndTime());
158 double triggerTime = GetTriggerTime();
160 if (triggerTime == 0)
161 /* flexible downtime has not been triggered yet */
164 return (now < triggerTime + GetDuration());
167 bool Downtime::IsTriggered() const
169 double now = Utility::GetTime();
171 double triggerTime = GetTriggerTime();
173 return (triggerTime > 0 && triggerTime <= now);
176 bool Downtime::IsExpired() const
178 double now = Utility::GetTime();
181 return (GetEndTime() < now);
183 /* triggered flexible downtime not in effect anymore */
184 if (IsTriggered() && !IsInEffect())
186 /* flexible downtime never triggered */
187 else if (!IsTriggered() && (GetEndTime() < now))
194 bool Downtime::HasValidConfigOwner() const
196 if (!ScheduledDowntime::AllConfigIsLoaded()) {
200 String configOwner = GetConfigOwner();
201 return configOwner.IsEmpty() || Zone::GetByName(GetAuthoritativeZone()) != Zone::GetLocalZone() || GetObject<ScheduledDowntime>(configOwner);
204 int Downtime::GetNextDowntimeID()
206 boost::mutex::scoped_lock lock(l_DowntimeMutex);
208 return l_NextDowntimeID;
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)
220 fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
224 Dictionary::Ptr attrs = new Dictionary();
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());
237 if (!scheduledDowntime.IsEmpty()) {
238 auto localZone (Zone::GetLocalZone());
241 attrs->Set("authoritative_zone", localZone->GetName());
246 Service::Ptr service;
247 tie(host, service) = GetHostService(checkable);
249 attrs->Set("host_name", host->GetName());
251 attrs->Set("service_name", service->GetShortName());
255 if (!scheduledDowntime.IsEmpty()) {
256 auto sdt (ScheduledDowntime::GetByName(scheduledDowntime));
259 auto sdtZone (sdt->GetZone());
262 zone = sdtZone->GetName();
267 if (zone.IsEmpty()) {
268 zone = checkable->GetZoneName();
272 attrs->Set("zone", zone);
274 String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, nullptr, attrs);
276 Array::Ptr errors = new Array();
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);
284 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime."));
287 if (!triggeredBy.IsEmpty()) {
288 Downtime::Ptr parentDowntime = Downtime::GetByName(triggeredBy);
289 Array::Ptr triggers = parentDowntime->GetTriggers();
291 ObjectLock olock(triggers);
292 if (!triggers->Contains(fullName))
293 triggers->Add(fullName);
296 Downtime::Ptr downtime = Downtime::GetByName(fullName);
299 BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime object."));
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) << "'.";
309 void Downtime::RemoveDowntime(const String& id, bool cancelled, bool expired, const MessageOrigin::Ptr& origin)
311 Downtime::Ptr downtime = Downtime::GetByName(id);
313 if (!downtime || downtime->GetPackage() != "_api")
316 String config_owner = downtime->GetConfigOwner();
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 << "'";
324 downtime->SetWasCancelled(cancelled);
326 Log(LogNotice, "Downtime")
327 << "Removed downtime '" << downtime->GetName() << "' from object '" << downtime->GetCheckable()->GetName() << "'.";
329 Array::Ptr errors = new Array();
331 if (!ConfigObjectUtility::DeleteObject(downtime, false, errors, nullptr)) {
332 ObjectLock olock(errors);
333 for (const String& error : errors) {
334 Log(LogCritical, "Downtime", error);
337 BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
341 bool Downtime::CanBeTriggered()
343 if (IsInEffect() && IsTriggered())
349 double now = Utility::GetTime();
351 if (now < GetStartTime() || now > GetEndTime())
357 void Downtime::TriggerDowntime()
359 if (!CanBeTriggered())
362 Log(LogNotice, "Downtime")
363 << "Triggering downtime '" << GetName() << "'.";
365 if (GetTriggerTime() == 0)
366 SetTriggerTime(Utility::GetTime());
368 Array::Ptr triggers = GetTriggers();
371 ObjectLock olock(triggers);
372 for (const String& triggerName : triggers) {
373 Downtime::Ptr downtime = Downtime::GetByName(triggerName);
378 downtime->TriggerDowntime();
382 OnDowntimeTriggered(this);
385 String Downtime::GetDowntimeIDFromLegacyID(int id)
387 boost::mutex::scoped_lock lock(l_DowntimeMutex);
389 auto it = l_LegacyDowntimesCache.find(id);
391 if (it == l_LegacyDowntimesCache.end())
397 void Downtime::DowntimesStartTimerHandler()
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);
407 /* Trigger fixed downtime immediately. */
408 downtime->TriggerDowntime();
413 void Downtime::DowntimesExpireTimerHandler()
415 std::vector<Downtime::Ptr> downtimes;
417 for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
418 downtimes.push_back(downtime);
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);
428 void Downtime::ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
430 ObjectImpl<Downtime>::ValidateStartTime(lvalue, utils);
433 BOOST_THROW_EXCEPTION(ValidationError(this, { "start_time" }, "Start time must be greater than 0."));
436 void Downtime::ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
438 ObjectImpl<Downtime>::ValidateEndTime(lvalue, utils);
441 BOOST_THROW_EXCEPTION(ValidationError(this, { "end_time" }, "End time must be greater than 0."));
444 DowntimeChildOptions Downtime::ChildOptionsFromValue(const Value& options)
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);
458 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid child option specified"));