summaryrefslogtreecommitdiffstats
path: root/lib/icinga
diff options
context:
space:
mode:
Diffstat (limited to 'lib/icinga')
-rw-r--r--lib/icinga/CMakeLists.txt76
-rw-r--r--lib/icinga/apiactions.cpp962
-rw-r--r--lib/icinga/apiactions.hpp42
-rw-r--r--lib/icinga/apievents.cpp438
-rw-r--r--lib/icinga/apievents.hpp51
-rw-r--r--lib/icinga/checkable-check.cpp709
-rw-r--r--lib/icinga/checkable-comment.cpp75
-rw-r--r--lib/icinga/checkable-dependency.cpp176
-rw-r--r--lib/icinga/checkable-downtime.cpp64
-rw-r--r--lib/icinga/checkable-event.cpp81
-rw-r--r--lib/icinga/checkable-flapping.cpp114
-rw-r--r--lib/icinga/checkable-notification.cpp334
-rw-r--r--lib/icinga/checkable-script.cpp28
-rw-r--r--lib/icinga/checkable.cpp322
-rw-r--r--lib/icinga/checkable.hpp264
-rw-r--r--lib/icinga/checkable.ti192
-rw-r--r--lib/icinga/checkcommand.cpp22
-rw-r--r--lib/icinga/checkcommand.hpp32
-rw-r--r--lib/icinga/checkcommand.ti14
-rw-r--r--lib/icinga/checkresult.cpp34
-rw-r--r--lib/icinga/checkresult.hpp28
-rw-r--r--lib/icinga/checkresult.ti72
-rw-r--r--lib/icinga/cib.cpp346
-rw-r--r--lib/icinga/cib.hpp91
-rw-r--r--lib/icinga/clusterevents-check.cpp379
-rw-r--r--lib/icinga/clusterevents.cpp1623
-rw-r--r--lib/icinga/clusterevents.hpp102
-rw-r--r--lib/icinga/command.cpp68
-rw-r--r--lib/icinga/command.hpp30
-rw-r--r--lib/icinga/command.ti54
-rw-r--r--lib/icinga/comment.cpp258
-rw-r--r--lib/icinga/comment.hpp59
-rw-r--r--lib/icinga/comment.ti80
-rw-r--r--lib/icinga/compatutility.cpp302
-rw-r--r--lib/icinga/compatutility.hpp56
-rw-r--r--lib/icinga/customvarobject.cpp49
-rw-r--r--lib/icinga/customvarobject.hpp31
-rw-r--r--lib/icinga/customvarobject.ti15
-rw-r--r--lib/icinga/dependency-apply.cpp161
-rw-r--r--lib/icinga/dependency.cpp325
-rw-r--r--lib/icinga/dependency.hpp62
-rw-r--r--lib/icinga/dependency.ti101
-rw-r--r--lib/icinga/downtime.cpp584
-rw-r--r--lib/icinga/downtime.hpp99
-rw-r--r--lib/icinga/downtime.ti82
-rw-r--r--lib/icinga/envresolver.cpp20
-rw-r--r--lib/icinga/envresolver.hpp30
-rw-r--r--lib/icinga/eventcommand.cpp20
-rw-r--r--lib/icinga/eventcommand.hpp32
-rw-r--r--lib/icinga/eventcommand.ti15
-rw-r--r--lib/icinga/externalcommandprocessor.cpp2281
-rw-r--r--lib/icinga/externalcommandprocessor.hpp169
-rw-r--r--lib/icinga/host.cpp330
-rw-r--r--lib/icinga/host.hpp71
-rw-r--r--lib/icinga/host.ti48
-rw-r--r--lib/icinga/hostgroup.cpp108
-rw-r--r--lib/icinga/hostgroup.hpp43
-rw-r--r--lib/icinga/hostgroup.ti28
-rw-r--r--lib/icinga/i2-icinga.hpp15
-rw-r--r--lib/icinga/icinga-itl.conf15
-rw-r--r--lib/icinga/icingaapplication.cpp321
-rw-r--r--lib/icinga/icingaapplication.hpp52
-rw-r--r--lib/icinga/icingaapplication.ti41
-rw-r--r--lib/icinga/legacytimeperiod.cpp644
-rw-r--r--lib/icinga/legacytimeperiod.hpp45
-rw-r--r--lib/icinga/macroprocessor.cpp585
-rw-r--r--lib/icinga/macroprocessor.hpp75
-rw-r--r--lib/icinga/macroresolver.hpp31
-rw-r--r--lib/icinga/notification-apply.cpp161
-rw-r--r--lib/icinga/notification.cpp812
-rw-r--r--lib/icinga/notification.hpp135
-rw-r--r--lib/icinga/notification.ti111
-rw-r--r--lib/icinga/notificationcommand.cpp27
-rw-r--r--lib/icinga/notificationcommand.hpp36
-rw-r--r--lib/icinga/notificationcommand.ti14
-rw-r--r--lib/icinga/objectutils.cpp55
-rw-r--r--lib/icinga/objectutils.hpp29
-rw-r--r--lib/icinga/pluginutility.cpp218
-rw-r--r--lib/icinga/pluginutility.hpp42
-rw-r--r--lib/icinga/scheduleddowntime-apply.cpp159
-rw-r--r--lib/icinga/scheduleddowntime.cpp393
-rw-r--r--lib/icinga/scheduleddowntime.hpp60
-rw-r--r--lib/icinga/scheduleddowntime.ti76
-rw-r--r--lib/icinga/service-apply.cpp133
-rw-r--r--lib/icinga/service.cpp287
-rw-r--r--lib/icinga/service.hpp65
-rw-r--r--lib/icinga/service.ti71
-rw-r--r--lib/icinga/servicegroup.cpp111
-rw-r--r--lib/icinga/servicegroup.hpp43
-rw-r--r--lib/icinga/servicegroup.ti28
-rw-r--r--lib/icinga/timeperiod.cpp399
-rw-r--r--lib/icinga/timeperiod.hpp50
-rw-r--r--lib/icinga/timeperiod.ti47
-rw-r--r--lib/icinga/user.cpp103
-rw-r--r--lib/icinga/user.hpp44
-rw-r--r--lib/icinga/user.ti47
-rw-r--r--lib/icinga/usergroup.cpp128
-rw-r--r--lib/icinga/usergroup.hpp49
-rw-r--r--lib/icinga/usergroup.ti25
99 files changed, 18064 insertions, 0 deletions
diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt
new file mode 100644
index 0000000..62077bc
--- /dev/null
+++ b/lib/icinga/CMakeLists.txt
@@ -0,0 +1,76 @@
+# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+
+
+mkclass_target(checkable.ti checkable-ti.cpp checkable-ti.hpp)
+mkclass_target(checkcommand.ti checkcommand-ti.cpp checkcommand-ti.hpp)
+mkclass_target(checkresult.ti checkresult-ti.cpp checkresult-ti.hpp)
+mkclass_target(command.ti command-ti.cpp command-ti.hpp)
+mkclass_target(comment.ti comment-ti.cpp comment-ti.hpp)
+mkclass_target(dependency.ti dependency-ti.cpp dependency-ti.hpp)
+mkclass_target(downtime.ti downtime-ti.cpp downtime-ti.hpp)
+mkclass_target(eventcommand.ti eventcommand-ti.cpp eventcommand-ti.hpp)
+mkclass_target(hostgroup.ti hostgroup-ti.cpp hostgroup-ti.hpp)
+mkclass_target(host.ti host-ti.cpp host-ti.hpp)
+mkclass_target(icingaapplication.ti icingaapplication-ti.cpp icingaapplication-ti.hpp)
+mkclass_target(customvarobject.ti customvarobject-ti.cpp customvarobject-ti.hpp)
+mkclass_target(notificationcommand.ti notificationcommand-ti.cpp notificationcommand-ti.hpp)
+mkclass_target(notification.ti notification-ti.cpp notification-ti.hpp)
+mkclass_target(scheduleddowntime.ti scheduleddowntime-ti.cpp scheduleddowntime-ti.hpp)
+mkclass_target(servicegroup.ti servicegroup-ti.cpp servicegroup-ti.hpp)
+mkclass_target(service.ti service-ti.cpp service-ti.hpp)
+mkclass_target(timeperiod.ti timeperiod-ti.cpp timeperiod-ti.hpp)
+mkclass_target(usergroup.ti usergroup-ti.cpp usergroup-ti.hpp)
+mkclass_target(user.ti user-ti.cpp user-ti.hpp)
+
+mkembedconfig_target(icinga-itl.conf icinga-itl.cpp)
+
+set(icinga_SOURCES
+ i2-icinga.hpp icinga-itl.cpp
+ apiactions.cpp apiactions.hpp
+ apievents.cpp apievents.hpp
+ checkable.cpp checkable.hpp checkable-ti.hpp
+ checkable-check.cpp checkable-comment.cpp checkable-dependency.cpp
+ checkable-downtime.cpp checkable-event.cpp checkable-flapping.cpp
+ checkable-notification.cpp checkable-script.cpp
+ checkcommand.cpp checkcommand.hpp checkcommand-ti.hpp
+ checkresult.cpp checkresult.hpp checkresult-ti.hpp
+ cib.cpp cib.hpp
+ clusterevents.cpp clusterevents.hpp clusterevents-check.cpp
+ command.cpp command.hpp command-ti.hpp
+ comment.cpp comment.hpp comment-ti.hpp
+ compatutility.cpp compatutility.hpp
+ customvarobject.cpp customvarobject.hpp customvarobject-ti.hpp
+ dependency.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp
+ downtime.cpp downtime.hpp downtime-ti.hpp
+ envresolver.cpp envresolver.hpp
+ eventcommand.cpp eventcommand.hpp eventcommand-ti.hpp
+ externalcommandprocessor.cpp externalcommandprocessor.hpp
+ host.cpp host.hpp host-ti.hpp
+ hostgroup.cpp hostgroup.hpp hostgroup-ti.hpp
+ icingaapplication.cpp icingaapplication.hpp icingaapplication-ti.hpp
+ legacytimeperiod.cpp legacytimeperiod.hpp
+ macroprocessor.cpp macroprocessor.hpp
+ macroresolver.hpp
+ notification.cpp notification.hpp notification-ti.hpp notification-apply.cpp
+ notificationcommand.cpp notificationcommand.hpp notificationcommand-ti.hpp
+ objectutils.cpp objectutils.hpp
+ pluginutility.cpp pluginutility.hpp
+ scheduleddowntime.cpp scheduleddowntime.hpp scheduleddowntime-ti.hpp scheduleddowntime-apply.cpp
+ service.cpp service.hpp service-ti.hpp service-apply.cpp
+ servicegroup.cpp servicegroup.hpp servicegroup-ti.hpp
+ timeperiod.cpp timeperiod.hpp timeperiod-ti.hpp
+ user.cpp user.hpp user-ti.hpp
+ usergroup.cpp usergroup.hpp usergroup-ti.hpp
+)
+
+if(ICINGA2_UNITY_BUILD)
+ mkunity_target(icinga icinga icinga_SOURCES)
+endif()
+
+add_library(icinga OBJECT ${icinga_SOURCES})
+
+add_dependencies(icinga base config remote)
+
+set_target_properties (
+ icinga PROPERTIES
+ FOLDER Lib
+)
diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp
new file mode 100644
index 0000000..885834e
--- /dev/null
+++ b/lib/icinga/apiactions.cpp
@@ -0,0 +1,962 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/apiactions.hpp"
+#include "icinga/checkable.hpp"
+#include "icinga/service.hpp"
+#include "icinga/servicegroup.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/pluginutility.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/clusterevents.hpp"
+#include "remote/apiaction.hpp"
+#include "remote/apilistener.hpp"
+#include "remote/configobjectslock.hpp"
+#include "remote/filterutility.hpp"
+#include "remote/pkiutility.hpp"
+#include "remote/httputility.hpp"
+#include "base/utility.hpp"
+#include "base/convert.hpp"
+#include "base/defer.hpp"
+#include "remote/actionshandler.hpp"
+#include <fstream>
+
+using namespace icinga;
+
+REGISTER_APIACTION(process_check_result, "Service;Host", &ApiActions::ProcessCheckResult);
+REGISTER_APIACTION(reschedule_check, "Service;Host", &ApiActions::RescheduleCheck);
+REGISTER_APIACTION(send_custom_notification, "Service;Host", &ApiActions::SendCustomNotification);
+REGISTER_APIACTION(delay_notification, "Service;Host", &ApiActions::DelayNotification);
+REGISTER_APIACTION(acknowledge_problem, "Service;Host", &ApiActions::AcknowledgeProblem);
+REGISTER_APIACTION(remove_acknowledgement, "Service;Host", &ApiActions::RemoveAcknowledgement);
+REGISTER_APIACTION(add_comment, "Service;Host", &ApiActions::AddComment);
+REGISTER_APIACTION(remove_comment, "Service;Host;Comment", &ApiActions::RemoveComment);
+REGISTER_APIACTION(schedule_downtime, "Service;Host", &ApiActions::ScheduleDowntime);
+REGISTER_APIACTION(remove_downtime, "Service;Host;Downtime", &ApiActions::RemoveDowntime);
+REGISTER_APIACTION(shutdown_process, "", &ApiActions::ShutdownProcess);
+REGISTER_APIACTION(restart_process, "", &ApiActions::RestartProcess);
+REGISTER_APIACTION(generate_ticket, "", &ApiActions::GenerateTicket);
+REGISTER_APIACTION(execute_command, "Service;Host", &ApiActions::ExecuteCommand);
+
+Dictionary::Ptr ApiActions::CreateResult(int code, const String& status,
+ const Dictionary::Ptr& additional)
+{
+ Dictionary::Ptr result = new Dictionary({
+ { "code", code },
+ { "status", status }
+ });
+
+ if (additional)
+ additional->CopyTo(result);
+
+ return result;
+}
+
+Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ using Result = Checkable::ProcessingResult;
+
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404,
+ "Cannot process passive check result for non-existent object.");
+
+ if (!checkable->GetEnablePassiveChecks())
+ return ApiActions::CreateResult(403, "Passive checks are disabled for object '" + checkable->GetName() + "'.");
+
+ if (!checkable->IsReachable(DependencyCheckExecution))
+ return ApiActions::CreateResult(200, "Ignoring passive check result for unreachable object '" + checkable->GetName() + "'.");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ if (!params->Contains("exit_status"))
+ return ApiActions::CreateResult(400, "Parameter 'exit_status' is required.");
+
+ int exitStatus = HttpUtility::GetLastParameter(params, "exit_status");
+
+ ServiceState state;
+
+ if (!service) {
+ if (exitStatus == 0)
+ state = ServiceOK;
+ else if (exitStatus == 1)
+ state = ServiceCritical;
+ else
+ return ApiActions::CreateResult(400, "Invalid 'exit_status' for Host "
+ + checkable->GetName() + ".");
+ } else {
+ state = PluginUtility::ExitStatusToState(exitStatus);
+ }
+
+ if (!params->Contains("plugin_output"))
+ return ApiActions::CreateResult(400, "Parameter 'plugin_output' is required");
+
+ CheckResult::Ptr cr = new CheckResult();
+ cr->SetOutput(HttpUtility::GetLastParameter(params, "plugin_output"));
+ cr->SetState(state);
+
+ if (params->Contains("execution_start"))
+ cr->SetExecutionStart(HttpUtility::GetLastParameter(params, "execution_start"));
+
+ if (params->Contains("execution_end"))
+ cr->SetExecutionEnd(HttpUtility::GetLastParameter(params, "execution_end"));
+
+ cr->SetCheckSource(HttpUtility::GetLastParameter(params, "check_source"));
+ cr->SetSchedulingSource(HttpUtility::GetLastParameter(params, "scheduling_source"));
+
+ Value perfData = params->Get("performance_data");
+
+ /* Allow to pass a performance data string from Icinga Web 2 next to the new Array notation. */
+ if (perfData.IsString())
+ cr->SetPerformanceData(PluginUtility::SplitPerfdata(perfData));
+ else
+ cr->SetPerformanceData(perfData);
+
+ cr->SetCommand(params->Get("check_command"));
+
+ /* Mark this check result as passive. */
+ cr->SetActive(false);
+
+ /* Result TTL allows to overrule the next expected freshness check. */
+ if (params->Contains("ttl"))
+ cr->SetTtl(HttpUtility::GetLastParameter(params, "ttl"));
+
+ Result result = checkable->ProcessCheckResult(cr);
+ switch (result) {
+ case Result::Ok:
+ return ApiActions::CreateResult(200, "Successfully processed check result for object '" + checkable->GetName() + "'.");
+ case Result::NoCheckResult:
+ return ApiActions::CreateResult(400, "Could not process check result for object '" + checkable->GetName() + "' because no check result was passed.");
+ case Result::CheckableInactive:
+ return ApiActions::CreateResult(503, "Could not process check result for object '" + checkable->GetName() + "' because the object is inactive.");
+ case Result::NewerCheckResultPresent:
+ return ApiActions::CreateResult(409, "Newer check result already present. Check result for '" + checkable->GetName() + "' was discarded.");
+ }
+
+ return ApiActions::CreateResult(500, "Unexpected result (" + std::to_string(static_cast<int>(result)) + ") for object '" + checkable->GetName() + "'. Please submit a bug report at https://github.com/Icinga/icinga2");
+}
+
+Dictionary::Ptr ApiActions::RescheduleCheck(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Cannot reschedule check for non-existent object.");
+
+ if (Convert::ToBool(HttpUtility::GetLastParameter(params, "force")))
+ checkable->SetForceNextCheck(true);
+
+ double nextCheck;
+ if (params->Contains("next_check"))
+ nextCheck = HttpUtility::GetLastParameter(params, "next_check");
+ else
+ nextCheck = Utility::GetTime();
+
+ checkable->SetNextCheck(nextCheck);
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(checkable);
+
+ return ApiActions::CreateResult(200, "Successfully rescheduled check for object '" + checkable->GetName() + "'.");
+}
+
+Dictionary::Ptr ApiActions::SendCustomNotification(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Cannot send notification for non-existent object.");
+
+ if (!params->Contains("author"))
+ return ApiActions::CreateResult(400, "Parameter 'author' is required.");
+
+ if (!params->Contains("comment"))
+ return ApiActions::CreateResult(400, "Parameter 'comment' is required.");
+
+ if (Convert::ToBool(HttpUtility::GetLastParameter(params, "force")))
+ checkable->SetForceNextNotification(true);
+
+ Checkable::OnNotificationsRequested(checkable, NotificationCustom, checkable->GetLastCheckResult(),
+ HttpUtility::GetLastParameter(params, "author"), HttpUtility::GetLastParameter(params, "comment"), nullptr);
+
+ return ApiActions::CreateResult(200, "Successfully sent custom notification for object '" + checkable->GetName() + "'.");
+}
+
+Dictionary::Ptr ApiActions::DelayNotification(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Cannot delay notifications for non-existent object");
+
+ if (!params->Contains("timestamp"))
+ return ApiActions::CreateResult(400, "A timestamp is required to delay notifications");
+
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ notification->SetNextNotification(HttpUtility::GetLastParameter(params, "timestamp"));
+ }
+
+ return ApiActions::CreateResult(200, "Successfully delayed notifications for object '" + checkable->GetName() + "'.");
+}
+
+Dictionary::Ptr ApiActions::AcknowledgeProblem(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Cannot acknowledge problem for non-existent object.");
+
+ if (!params->Contains("author") || !params->Contains("comment"))
+ return ApiActions::CreateResult(400, "Acknowledgements require author and comment.");
+
+ AcknowledgementType sticky = AcknowledgementNormal;
+ bool notify = false;
+ bool persistent = false;
+ double timestamp = 0.0;
+
+ if (params->Contains("sticky") && HttpUtility::GetLastParameter(params, "sticky"))
+ sticky = AcknowledgementSticky;
+ if (params->Contains("notify"))
+ notify = HttpUtility::GetLastParameter(params, "notify");
+ if (params->Contains("persistent"))
+ persistent = HttpUtility::GetLastParameter(params, "persistent");
+ if (params->Contains("expiry")) {
+ timestamp = HttpUtility::GetLastParameter(params, "expiry");
+
+ if (timestamp <= Utility::GetTime())
+ return ApiActions::CreateResult(409, "Acknowledgement 'expiry' timestamp must be in the future for object " + checkable->GetName());
+ } else
+ timestamp = 0;
+
+ ObjectLock oLock (checkable);
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ if (!service) {
+ if (host->GetState() == HostUp)
+ return ApiActions::CreateResult(409, "Host " + checkable->GetName() + " is UP.");
+ } else {
+ if (service->GetState() == ServiceOK)
+ return ApiActions::CreateResult(409, "Service " + checkable->GetName() + " is OK.");
+ }
+
+ if (checkable->IsAcknowledged()) {
+ return ApiActions::CreateResult(409, (service ? "Service " : "Host ") + checkable->GetName() + " is already acknowledged.");
+ }
+
+ ConfigObjectsSharedLock lock (std::try_to_lock);
+
+ if (!lock) {
+ return ApiActions::CreateResult(503, "Icinga is reloading.");
+ }
+
+ Comment::AddComment(checkable, CommentAcknowledgement, HttpUtility::GetLastParameter(params, "author"),
+ HttpUtility::GetLastParameter(params, "comment"), persistent, timestamp, sticky == AcknowledgementSticky);
+ checkable->AcknowledgeProblem(HttpUtility::GetLastParameter(params, "author"),
+ HttpUtility::GetLastParameter(params, "comment"), sticky, notify, persistent, Utility::GetTime(), timestamp);
+
+ return ApiActions::CreateResult(200, "Successfully acknowledged problem for object '" + checkable->GetName() + "'.");
+}
+
+Dictionary::Ptr ApiActions::RemoveAcknowledgement(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404,
+ "Cannot remove acknowledgement for non-existent checkable object "
+ + object->GetName() + ".");
+
+ ConfigObjectsSharedLock lock (std::try_to_lock);
+
+ if (!lock) {
+ return ApiActions::CreateResult(503, "Icinga is reloading.");
+ }
+
+ String removedBy (HttpUtility::GetLastParameter(params, "author"));
+
+ checkable->ClearAcknowledgement(removedBy);
+ checkable->RemoveAckComments(removedBy);
+
+ return ApiActions::CreateResult(200, "Successfully removed acknowledgement for object '" + checkable->GetName() + "'.");
+}
+
+Dictionary::Ptr ApiActions::AddComment(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Cannot add comment for non-existent object");
+
+ if (!params->Contains("author") || !params->Contains("comment"))
+ return ApiActions::CreateResult(400, "Comments require author and comment.");
+
+ double timestamp = 0.0;
+
+ if (params->Contains("expiry")) {
+ timestamp = HttpUtility::GetLastParameter(params, "expiry");
+ }
+
+ ConfigObjectsSharedLock lock (std::try_to_lock);
+
+ if (!lock) {
+ return ApiActions::CreateResult(503, "Icinga is reloading.");
+ }
+
+ String commentName = Comment::AddComment(checkable, CommentUser,
+ HttpUtility::GetLastParameter(params, "author"),
+ HttpUtility::GetLastParameter(params, "comment"), false, timestamp);
+
+ Comment::Ptr comment = Comment::GetByName(commentName);
+
+ Dictionary::Ptr additional = new Dictionary({
+ { "name", commentName },
+ { "legacy_id", comment->GetLegacyId() }
+ });
+
+ return ApiActions::CreateResult(200, "Successfully added comment '"
+ + commentName + "' for object '" + checkable->GetName()
+ + "'.", additional);
+}
+
+Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ ConfigObjectsSharedLock lock (std::try_to_lock);
+
+ if (!lock) {
+ return ApiActions::CreateResult(503, "Icinga is reloading.");
+ }
+
+ auto author (HttpUtility::GetLastParameter(params, "author"));
+ Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
+
+ if (checkable) {
+ std::set<Comment::Ptr> comments = checkable->GetComments();
+
+ for (const Comment::Ptr& comment : comments) {
+ Comment::RemoveComment(comment->GetName(), true, author);
+ }
+
+ return ApiActions::CreateResult(200, "Successfully removed all comments for object '" + checkable->GetName() + "'.");
+ }
+
+ Comment::Ptr comment = static_pointer_cast<Comment>(object);
+
+ if (!comment)
+ return ApiActions::CreateResult(404, "Cannot remove non-existent comment object.");
+
+ String commentName = comment->GetName();
+
+ Comment::RemoveComment(commentName, true, author);
+
+ return ApiActions::CreateResult(200, "Successfully removed comment '" + commentName + "'.");
+}
+
+Dictionary::Ptr ApiActions::ScheduleDowntime(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Can't schedule downtime for non-existent object.");
+
+ if (!params->Contains("start_time") || !params->Contains("end_time") ||
+ !params->Contains("author") || !params->Contains("comment")) {
+
+ return ApiActions::CreateResult(400, "Options 'start_time', 'end_time', 'author' and 'comment' are required");
+ }
+
+ bool fixed = true;
+ if (params->Contains("fixed"))
+ fixed = HttpUtility::GetLastParameter(params, "fixed");
+
+ if (!fixed && !params->Contains("duration"))
+ return ApiActions::CreateResult(400, "Option 'duration' is required for flexible downtime");
+
+ double duration = 0.0;
+ if (params->Contains("duration"))
+ duration = HttpUtility::GetLastParameter(params, "duration");
+
+ String triggerName;
+ if (params->Contains("trigger_name"))
+ triggerName = HttpUtility::GetLastParameter(params, "trigger_name");
+
+ String author = HttpUtility::GetLastParameter(params, "author");
+ String comment = HttpUtility::GetLastParameter(params, "comment");
+ double startTime = HttpUtility::GetLastParameter(params, "start_time");
+ double endTime = HttpUtility::GetLastParameter(params, "end_time");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ DowntimeChildOptions childOptions = DowntimeNoChildren;
+ if (params->Contains("child_options")) {
+ try {
+ childOptions = Downtime::ChildOptionsFromValue(HttpUtility::GetLastParameter(params, "child_options"));
+ } catch (const std::exception&) {
+ return ApiActions::CreateResult(400, "Option 'child_options' provided an invalid value.");
+ }
+ }
+
+ ConfigObjectsSharedLock lock (std::try_to_lock);
+
+ if (!lock) {
+ return ApiActions::CreateResult(503, "Icinga is reloading.");
+ }
+
+ Downtime::Ptr downtime = Downtime::AddDowntime(checkable, author, comment, startTime, endTime,
+ fixed, triggerName, duration);
+ String downtimeName = downtime->GetName();
+
+ Dictionary::Ptr additional = new Dictionary({
+ { "name", downtimeName },
+ { "legacy_id", downtime->GetLegacyId() }
+ });
+
+ /* Schedule downtime for all services for the host type. */
+ bool allServices = false;
+
+ if (params->Contains("all_services"))
+ allServices = HttpUtility::GetLastParameter(params, "all_services");
+
+ if (allServices && !service) {
+ ArrayData serviceDowntimes;
+
+ for (const Service::Ptr& hostService : host->GetServices()) {
+ Log(LogNotice, "ApiActions")
+ << "Creating downtime for service " << hostService->GetName() << " on host " << host->GetName();
+
+ Downtime::Ptr serviceDowntime = Downtime::AddDowntime(hostService, author, comment, startTime, endTime,
+ fixed, triggerName, duration, String(), String(), downtimeName);
+ String serviceDowntimeName = serviceDowntime->GetName();
+
+ serviceDowntimes.push_back(new Dictionary({
+ { "name", serviceDowntimeName },
+ { "legacy_id", serviceDowntime->GetLegacyId() }
+ }));
+ }
+
+ additional->Set("service_downtimes", new Array(std::move(serviceDowntimes)));
+ }
+
+ /* Schedule downtime for all child objects. */
+ if (childOptions != DowntimeNoChildren) {
+ /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime.
+ * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children.
+ */
+ if (childOptions == DowntimeTriggeredChildren)
+ triggerName = downtimeName;
+
+ Log(LogNotice, "ApiActions")
+ << "Processing child options " << childOptions << " for downtime " << downtimeName;
+
+ ArrayData childDowntimes;
+
+ std::set<Checkable::Ptr> allChildren = checkable->GetAllChildren();
+ for (const Checkable::Ptr& child : allChildren) {
+ Host::Ptr childHost;
+ Service::Ptr childService;
+ tie(childHost, childService) = GetHostService(child);
+
+ if (allServices && childService &&
+ allChildren.find(static_pointer_cast<Checkable>(childHost)) != allChildren.end()) {
+ /* When scheduling downtimes for all service and all children, the current child is a service, and its
+ * host is also a child, skip it here. The downtime for this service will be scheduled below together
+ * with the downtimes of all services for that host. Scheduling it below ensures that the relation
+ * from the child service downtime to the child host downtime is set properly. */
+ continue;
+ }
+
+ Log(LogNotice, "ApiActions")
+ << "Scheduling downtime for child object " << child->GetName();
+
+ Downtime::Ptr childDowntime = Downtime::AddDowntime(child, author, comment, startTime, endTime,
+ fixed, triggerName, duration);
+ String childDowntimeName = childDowntime->GetName();
+
+ Log(LogNotice, "ApiActions")
+ << "Add child downtime '" << childDowntimeName << "'.";
+
+ Dictionary::Ptr childAdditional = new Dictionary({
+ { "name", childDowntimeName },
+ { "legacy_id", childDowntime->GetLegacyId() }
+ });
+
+ /* For a host, also schedule all service downtimes if requested. */
+ if (allServices && !childService) {
+ ArrayData childServiceDowntimes;
+
+ for (const Service::Ptr& childService : childHost->GetServices()) {
+ Log(LogNotice, "ApiActions")
+ << "Creating downtime for service " << childService->GetName() << " on child host " << childHost->GetName();
+
+ Downtime::Ptr serviceDowntime = Downtime::AddDowntime(childService, author, comment, startTime, endTime,
+ fixed, triggerName, duration, String(), String(), childDowntimeName);
+ String serviceDowntimeName = serviceDowntime->GetName();
+
+ childServiceDowntimes.push_back(new Dictionary({
+ { "name", serviceDowntimeName },
+ { "legacy_id", serviceDowntime->GetLegacyId() }
+ }));
+ }
+
+ childAdditional->Set("service_downtimes", new Array(std::move(childServiceDowntimes)));
+ }
+
+ childDowntimes.push_back(childAdditional);
+ }
+
+ additional->Set("child_downtimes", new Array(std::move(childDowntimes)));
+ }
+
+ return ApiActions::CreateResult(200, "Successfully scheduled downtime '" +
+ downtimeName + "' for object '" + checkable->GetName() + "'.", additional);
+}
+
+Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ ConfigObjectsSharedLock lock (std::try_to_lock);
+
+ if (!lock) {
+ return ApiActions::CreateResult(503, "Icinga is reloading.");
+ }
+
+ auto author (HttpUtility::GetLastParameter(params, "author"));
+ Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
+
+ size_t childCount = 0;
+
+ if (checkable) {
+ std::set<Downtime::Ptr> downtimes = checkable->GetDowntimes();
+
+ for (const Downtime::Ptr& downtime : downtimes) {
+ childCount += downtime->GetChildren().size();
+
+ try {
+ Downtime::RemoveDowntime(downtime->GetName(), true, true, false, author);
+ } catch (const invalid_downtime_removal_error& error) {
+ Log(LogWarning, "ApiActions") << error.what();
+
+ return ApiActions::CreateResult(400, error.what());
+ }
+ }
+
+ return ApiActions::CreateResult(200, "Successfully removed all downtimes for object '" +
+ checkable->GetName() + "' and " + std::to_string(childCount) + " child downtimes.");
+ }
+
+ Downtime::Ptr downtime = static_pointer_cast<Downtime>(object);
+
+ if (!downtime)
+ return ApiActions::CreateResult(404, "Cannot remove non-existent downtime object.");
+
+ childCount += downtime->GetChildren().size();
+
+ try {
+ String downtimeName = downtime->GetName();
+ Downtime::RemoveDowntime(downtimeName, true, true, false, author);
+
+ return ApiActions::CreateResult(200, "Successfully removed downtime '" + downtimeName +
+ "' and " + std::to_string(childCount) + " child downtimes.");
+ } catch (const invalid_downtime_removal_error& error) {
+ Log(LogWarning, "ApiActions") << error.what();
+
+ return ApiActions::CreateResult(400, error.what());
+ }
+}
+
+Dictionary::Ptr ApiActions::ShutdownProcess(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Application::RequestShutdown();
+
+ return ApiActions::CreateResult(200, "Shutting down Icinga 2.");
+}
+
+Dictionary::Ptr ApiActions::RestartProcess(const ConfigObject::Ptr& object,
+ const Dictionary::Ptr& params)
+{
+ Application::RequestRestart();
+
+ return ApiActions::CreateResult(200, "Restarting Icinga 2.");
+}
+
+Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&,
+ const Dictionary::Ptr& params)
+{
+ if (!params->Contains("cn"))
+ return ApiActions::CreateResult(400, "Option 'cn' is required");
+
+ String cn = HttpUtility::GetLastParameter(params, "cn");
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+ String salt = listener->GetTicketSalt();
+
+ if (salt.IsEmpty())
+ return ApiActions::CreateResult(500, "Ticket salt is not configured in ApiListener object");
+
+ String ticket = PBKDF2_SHA1(cn, salt, 50000);
+
+ Dictionary::Ptr additional = new Dictionary({
+ { "ticket", ticket }
+ });
+
+ return ApiActions::CreateResult(200, "Generated PKI ticket '" + ticket + "' for common name '"
+ + cn + "'.", additional);
+}
+
+Value ApiActions::GetSingleObjectByNameUsingPermissions(const String& type, const String& objectName, const ApiUser::Ptr& user)
+{
+ Dictionary::Ptr queryParams = new Dictionary();
+ queryParams->Set("type", type);
+ queryParams->Set(type.ToLower(), objectName);
+
+ QueryDescription qd;
+ qd.Types.insert(type);
+ qd.Permission = "objects/query/" + type;
+
+ std::vector<Value> objs;
+
+ try {
+ objs = FilterUtility::GetFilterTargets(qd, queryParams, user);
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "ApiActions") << DiagnosticInformation(ex);
+ return nullptr;
+ }
+
+ if (objs.empty())
+ return nullptr;
+
+ return objs.at(0);
+};
+
+Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, const Dictionary::Ptr& params)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("No ApiListener instance configured."));
+
+ /* Get command_type */
+ String command_type = "EventCommand";
+
+ if (params->Contains("command_type"))
+ command_type = HttpUtility::GetLastParameter(params, "command_type");
+
+ /* Validate command_type */
+ if (command_type != "EventCommand" && command_type != "CheckCommand" && command_type != "NotificationCommand")
+ return ApiActions::CreateResult(400, "Invalid command_type '" + command_type + "'.");
+
+ Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
+
+ if (!checkable)
+ return ApiActions::CreateResult(404, "Can't start a command execution for a non-existent object.");
+
+ /* Get TTL param */
+ if (!params->Contains("ttl"))
+ return ApiActions::CreateResult(400, "Parameter ttl is required.");
+
+ double ttl = HttpUtility::GetLastParameter(params, "ttl");
+
+ if (ttl <= 0)
+ return ApiActions::CreateResult(400, "Parameter ttl must be greater than 0.");
+
+ ObjectLock oLock (checkable);
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ String endpoint = "$command_endpoint$";
+
+ if (params->Contains("endpoint"))
+ endpoint = HttpUtility::GetLastParameter(params, "endpoint");
+
+ MacroProcessor::ResolverList resolvers;
+ Value macros;
+
+ if (params->Contains("macros")) {
+ macros = HttpUtility::GetLastParameter(params, "macros");
+ if (macros.IsObjectType<Dictionary>()) {
+ resolvers.emplace_back("override", macros);
+ } else {
+ return ApiActions::CreateResult(400, "Parameter macros must be a dictionary.");
+ }
+ }
+
+ if (service)
+ resolvers.emplace_back("service", service);
+
+ resolvers.emplace_back("host", host);
+
+ String resolved_endpoint = MacroProcessor::ResolveMacros(
+ endpoint, resolvers, checkable->GetLastCheckResult(),
+ nullptr, MacroProcessor::EscapeCallback(), nullptr, false
+ );
+
+ if (!ActionsHandler::AuthenticatedApiUser)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Can't find API user."));
+
+ /* Get endpoint */
+ Endpoint::Ptr endpointPtr = GetSingleObjectByNameUsingPermissions(Endpoint::GetTypeName(), resolved_endpoint, ActionsHandler::AuthenticatedApiUser);
+
+ if (!endpointPtr)
+ return ApiActions::CreateResult(404, "Can't find a valid endpoint for '" + resolved_endpoint + "'.");
+
+ /* Return an error when
+ * the endpoint is different from the command endpoint of the checkable
+ * and the endpoint zone can't access the checkable.
+ * The endpoints are checked to allow for the case where command_endpoint is specified in the checkable
+ * but checkable is not actually present in the agent.
+ */
+ Zone::Ptr endpointZone = endpointPtr->GetZone();
+ Endpoint::Ptr commandEndpoint = checkable->GetCommandEndpoint();
+ if (endpointPtr != commandEndpoint && !endpointZone->CanAccessObject(checkable)) {
+ return ApiActions::CreateResult(
+ 409,
+ "Zone '" + endpointZone->GetName() + "' cannot access checkable '" + checkable->GetName() + "'."
+ );
+ }
+
+ /* Get command */
+ String command;
+
+ if (!params->Contains("command")) {
+ if (command_type == "CheckCommand" ) {
+ command = "$check_command$";
+ } else if (command_type == "EventCommand") {
+ command = "$event_command$";
+ } else if (command_type == "NotificationCommand") {
+ command = "$notification_command$";
+ }
+ } else {
+ command = HttpUtility::GetLastParameter(params, "command");
+ }
+
+ /* Resolve command macro */
+ String resolved_command = MacroProcessor::ResolveMacros(
+ command, resolvers, checkable->GetLastCheckResult(), nullptr,
+ MacroProcessor::EscapeCallback(), nullptr, false
+ );
+
+ CheckResult::Ptr cr = checkable->GetLastCheckResult();
+
+ if (!cr)
+ cr = new CheckResult();
+
+ /* Check if resolved_command exists and it is of type command_type */
+ Dictionary::Ptr execMacros = new Dictionary();
+
+ MacroResolver::OverrideMacros = macros;
+ Defer o ([]() {
+ MacroResolver::OverrideMacros = nullptr;
+ });
+
+ /* Create execution parameters */
+ Dictionary::Ptr execParams = new Dictionary();
+
+ if (command_type == "CheckCommand") {
+ CheckCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(CheckCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
+
+ if (!cmd)
+ return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
+ else {
+ CheckCommand::ExecuteOverride = cmd;
+ Defer resetCheckCommandOverride([]() {
+ CheckCommand::ExecuteOverride = nullptr;
+ });
+ cmd->Execute(checkable, cr, execMacros, false);
+ }
+ } else if (command_type == "EventCommand") {
+ EventCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(EventCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
+
+ if (!cmd)
+ return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
+ else {
+ EventCommand::ExecuteOverride = cmd;
+ Defer resetEventCommandOverride ([]() {
+ EventCommand::ExecuteOverride = nullptr;
+ });
+ cmd->Execute(checkable, execMacros, false);
+ }
+ } else if (command_type == "NotificationCommand") {
+ NotificationCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(NotificationCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
+
+ if (!cmd)
+ return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
+ else {
+ /* Get user */
+ String user_string = "";
+
+ if (params->Contains("user"))
+ user_string = HttpUtility::GetLastParameter(params, "user");
+
+ /* Resolve user macro */
+ String resolved_user = MacroProcessor::ResolveMacros(
+ user_string, resolvers, checkable->GetLastCheckResult(), nullptr,
+ MacroProcessor::EscapeCallback(), nullptr, false
+ );
+
+ User::Ptr user = GetSingleObjectByNameUsingPermissions(User::GetTypeName(), resolved_user, ActionsHandler::AuthenticatedApiUser);
+
+ if (!user)
+ return ApiActions::CreateResult(404, "Can't find a valid user for '" + resolved_user + "'.");
+
+ execParams->Set("user", user->GetName());
+
+ /* Get notification */
+ String notification_string = "";
+
+ if (params->Contains("notification"))
+ notification_string = HttpUtility::GetLastParameter(params, "notification");
+
+ /* Resolve notification macro */
+ String resolved_notification = MacroProcessor::ResolveMacros(
+ notification_string, resolvers, checkable->GetLastCheckResult(), nullptr,
+ MacroProcessor::EscapeCallback(), nullptr, false
+ );
+
+ Notification::Ptr notification = GetSingleObjectByNameUsingPermissions(Notification::GetTypeName(), resolved_notification, ActionsHandler::AuthenticatedApiUser);
+
+ if (!notification)
+ return ApiActions::CreateResult(404, "Can't find a valid notification for '" + resolved_notification + "'.");
+
+ execParams->Set("notification", notification->GetName());
+
+ NotificationCommand::ExecuteOverride = cmd;
+ Defer resetNotificationCommandOverride ([]() {
+ NotificationCommand::ExecuteOverride = nullptr;
+ });
+
+ cmd->Execute(notification, user, cr, NotificationType::NotificationCustom,
+ ActionsHandler::AuthenticatedApiUser->GetName(), "", execMacros, false);
+ }
+ }
+
+ /* This generates a UUID */
+ String uuid = Utility::NewUniqueID();
+
+ /* Create the deadline */
+ double deadline = Utility::GetTime() + ttl;
+
+ /* Update executions */
+ Dictionary::Ptr pending_execution = new Dictionary();
+ pending_execution->Set("pending", true);
+ pending_execution->Set("deadline", deadline);
+ pending_execution->Set("endpoint", resolved_endpoint);
+ Dictionary::Ptr executions = checkable->GetExecutions();
+
+ if (!executions)
+ executions = new Dictionary();
+
+ executions->Set(uuid, pending_execution);
+ checkable->SetExecutions(executions);
+
+ /* Broadcast the update */
+ Dictionary::Ptr executionsToBroadcast = new Dictionary();
+ executionsToBroadcast->Set(uuid, pending_execution);
+ Dictionary::Ptr updateParams = new Dictionary();
+ updateParams->Set("host", host->GetName());
+
+ if (service)
+ updateParams->Set("service", service->GetShortName());
+
+ updateParams->Set("executions", executionsToBroadcast);
+
+ Dictionary::Ptr updateMessage = new Dictionary();
+ updateMessage->Set("jsonrpc", "2.0");
+ updateMessage->Set("method", "event::UpdateExecutions");
+ updateMessage->Set("params", updateParams);
+
+ MessageOrigin::Ptr origin = new MessageOrigin();
+ listener->RelayMessage(origin, checkable, updateMessage, true);
+
+ /* Populate execution parameters */
+ if (command_type == "CheckCommand")
+ execParams->Set("command_type", "check_command");
+ else if (command_type == "EventCommand")
+ execParams->Set("command_type", "event_command");
+ else if (command_type == "NotificationCommand")
+ execParams->Set("command_type", "notification_command");
+
+ execParams->Set("command", resolved_command);
+ execParams->Set("host", host->GetName());
+
+ if (service)
+ execParams->Set("service", service->GetShortName());
+
+ /*
+ * If the host/service object specifies the 'check_timeout' attribute,
+ * forward this to the remote endpoint to limit the command execution time.
+ */
+ if (!checkable->GetCheckTimeout().IsEmpty())
+ execParams->Set("check_timeout", checkable->GetCheckTimeout());
+
+ execParams->Set("source", uuid);
+ execParams->Set("deadline", deadline);
+ execParams->Set("macros", execMacros);
+ execParams->Set("endpoint", resolved_endpoint);
+
+ /* Execute command */
+ bool local = endpointPtr == Endpoint::GetLocalEndpoint();
+
+ if (local) {
+ ClusterEvents::ExecuteCommandAPIHandler(origin, execParams);
+ } else {
+ /* Check if the child endpoints have Icinga version >= 2.13 */
+ Zone::Ptr localZone = Zone::GetLocalZone();
+ for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
+ /* Fetch immediate child zone members */
+ if (zone->GetParent() == localZone && zone->CanAccessObject(endpointPtr->GetZone())) {
+ std::set<Endpoint::Ptr> endpoints = zone->GetEndpoints();
+
+ for (const Endpoint::Ptr& childEndpoint : endpoints) {
+ if (!(childEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand)) {
+ /* Update execution */
+ double now = Utility::GetTime();
+ pending_execution->Set("exit", 126);
+ pending_execution->Set("output", "Endpoint '" + childEndpoint->GetName() + "' doesn't support executing arbitrary commands.");
+ pending_execution->Set("start", now);
+ pending_execution->Set("end", now);
+ pending_execution->Remove("pending");
+
+ listener->RelayMessage(origin, checkable, updateMessage, true);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("checkable", checkable->GetName());
+ result->Set("execution", uuid);
+ return ApiActions::CreateResult(202, "Accepted", result);
+ }
+ }
+ }
+ }
+
+ Dictionary::Ptr execMessage = new Dictionary();
+ execMessage->Set("jsonrpc", "2.0");
+ execMessage->Set("method", "event::ExecuteCommand");
+ execMessage->Set("params", execParams);
+
+ listener->RelayMessage(origin, endpointPtr->GetZone(), execMessage, true);
+ }
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("checkable", checkable->GetName());
+ result->Set("execution", uuid);
+ return ApiActions::CreateResult(202, "Accepted", result);
+}
diff --git a/lib/icinga/apiactions.hpp b/lib/icinga/apiactions.hpp
new file mode 100644
index 0000000..b6ba835
--- /dev/null
+++ b/lib/icinga/apiactions.hpp
@@ -0,0 +1,42 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef APIACTIONS_H
+#define APIACTIONS_H
+
+#include "icinga/i2-icinga.hpp"
+#include "base/configobject.hpp"
+#include "base/dictionary.hpp"
+#include "remote/apiuser.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup icinga
+ */
+class ApiActions
+{
+public:
+ static Dictionary::Ptr ProcessCheckResult(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RescheduleCheck(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr SendCustomNotification(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr DelayNotification(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr AcknowledgeProblem(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RemoveAcknowledgement(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr AddComment(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RemoveComment(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ScheduleDowntime(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RemoveDowntime(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ShutdownProcess(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RestartProcess(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr GenerateTicket(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ExecuteCommand(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+
+private:
+ static Dictionary::Ptr CreateResult(int code, const String& status, const Dictionary::Ptr& additional = nullptr);
+ static Value GetSingleObjectByNameUsingPermissions(const String& type, const String& value, const ApiUser::Ptr& user);
+};
+
+}
+
+#endif /* APIACTIONS_H */
diff --git a/lib/icinga/apievents.cpp b/lib/icinga/apievents.cpp
new file mode 100644
index 0000000..53008fd
--- /dev/null
+++ b/lib/icinga/apievents.cpp
@@ -0,0 +1,438 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/apievents.hpp"
+#include "icinga/service.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "base/initialize.hpp"
+#include "base/serializer.hpp"
+#include "base/logger.hpp"
+
+using namespace icinga;
+
+INITIALIZE_ONCE(&ApiEvents::StaticInitialize);
+
+void ApiEvents::StaticInitialize()
+{
+ Checkable::OnNewCheckResult.connect(&ApiEvents::CheckResultHandler);
+ Checkable::OnStateChange.connect(&ApiEvents::StateChangeHandler);
+ Checkable::OnNotificationSentToAllUsers.connect(&ApiEvents::NotificationSentToAllUsersHandler);
+
+ Checkable::OnFlappingChanged.connect(&ApiEvents::FlappingChangedHandler);
+
+ Checkable::OnAcknowledgementSet.connect(&ApiEvents::AcknowledgementSetHandler);
+ Checkable::OnAcknowledgementCleared.connect(&ApiEvents::AcknowledgementClearedHandler);
+
+ Comment::OnCommentAdded.connect(&ApiEvents::CommentAddedHandler);
+ Comment::OnCommentRemoved.connect(&ApiEvents::CommentRemovedHandler);
+
+ Downtime::OnDowntimeAdded.connect(&ApiEvents::DowntimeAddedHandler);
+ Downtime::OnDowntimeRemoved.connect(&ApiEvents::DowntimeRemovedHandler);
+ Downtime::OnDowntimeStarted.connect(&ApiEvents::DowntimeStartedHandler);
+ Downtime::OnDowntimeTriggered.connect(&ApiEvents::DowntimeTriggeredHandler);
+
+ ConfigObject::OnActiveChanged.connect(&ApiEvents::OnActiveChangedHandler);
+ ConfigObject::OnVersionChanged.connect(&ApiEvents::OnVersionChangedHandler);
+}
+
+void ApiEvents::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("CheckResult");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::CheckResult));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'CheckResult'.");
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("type", "CheckResult");
+ result->Set("timestamp", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ result->Set("host", host->GetName());
+ if (service)
+ result->Set("service", service->GetShortName());
+
+ result->Set("check_result", Serialize(cr));
+
+ result->Set("downtime_depth", checkable->GetDowntimeDepth());
+ result->Set("acknowledgement", checkable->IsAcknowledged());
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type, const MessageOrigin::Ptr& origin)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("StateChange");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::StateChange));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'StateChange'.");
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("type", "StateChange");
+ result->Set("timestamp", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ result->Set("host", host->GetName());
+ if (service)
+ result->Set("service", service->GetShortName());
+
+ result->Set("state", service ? static_cast<int>(service->GetState()) : static_cast<int>(host->GetState()));
+ result->Set("state_type", checkable->GetStateType());
+ result->Set("check_result", Serialize(cr));
+
+ result->Set("downtime_depth", checkable->GetDowntimeDepth());
+ result->Set("acknowledgement", checkable->IsAcknowledged());
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::NotificationSentToAllUsersHandler(const Notification::Ptr& notification,
+ const Checkable::Ptr& checkable, const std::set<User::Ptr>& users, NotificationType type,
+ const CheckResult::Ptr& cr, const String& author, const String& text, const MessageOrigin::Ptr& origin)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("Notification");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::Notification));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'Notification'.");
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("type", "Notification");
+ result->Set("timestamp", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ result->Set("host", host->GetName());
+ if (service)
+ result->Set("service", service->GetShortName());
+
+ NotificationCommand::Ptr command = notification->GetCommand();
+
+ if (command)
+ result->Set("command", command->GetName());
+
+ ArrayData userNames;
+
+ for (const User::Ptr& user : users) {
+ userNames.push_back(user->GetName());
+ }
+
+ result->Set("users", new Array(std::move(userNames)));
+ result->Set("notification_type", Notification::NotificationTypeToStringCompat(type)); //TODO: Change this to our own types.
+ result->Set("author", author);
+ result->Set("text", text);
+ result->Set("check_result", Serialize(cr));
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::FlappingChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("Flapping");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::Flapping));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'Flapping'.");
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("type", "Flapping");
+ result->Set("timestamp", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ result->Set("host", host->GetName());
+ if (service)
+ result->Set("service", service->GetShortName());
+
+ result->Set("state", service ? static_cast<int>(service->GetState()) : static_cast<int>(host->GetState()));
+ result->Set("state_type", checkable->GetStateType());
+ result->Set("is_flapping", checkable->IsFlapping());
+ result->Set("flapping_current", checkable->GetFlappingCurrent());
+ result->Set("threshold_low", checkable->GetFlappingThresholdLow());
+ result->Set("threshold_high", checkable->GetFlappingThresholdHigh());
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::AcknowledgementSetHandler(const Checkable::Ptr& checkable,
+ const String& author, const String& comment, AcknowledgementType type,
+ bool notify, bool persistent, double, double expiry, const MessageOrigin::Ptr& origin)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("AcknowledgementSet");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::AcknowledgementSet));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'AcknowledgementSet'.");
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("type", "AcknowledgementSet");
+ result->Set("timestamp", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ result->Set("host", host->GetName());
+ if (service)
+ result->Set("service", service->GetShortName());
+
+ result->Set("state", service ? static_cast<int>(service->GetState()) : static_cast<int>(host->GetState()));
+ result->Set("state_type", checkable->GetStateType());
+
+ result->Set("author", author);
+ result->Set("comment", comment);
+ result->Set("acknowledgement_type", type);
+ result->Set("notify", notify);
+ result->Set("persistent", persistent);
+ result->Set("expiry", expiry);
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double, const MessageOrigin::Ptr& origin)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("AcknowledgementCleared");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::AcknowledgementCleared));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'AcknowledgementCleared'.");
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("type", "AcknowledgementCleared");
+ result->Set("timestamp", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ result->Set("host", host->GetName());
+ if (service)
+ result->Set("service", service->GetShortName());
+
+ result->Set("state", service ? static_cast<int>(service->GetState()) : static_cast<int>(host->GetState()));
+ result->Set("state_type", checkable->GetStateType());
+ result->Set("acknowledgement_type", AcknowledgementNone);
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::CommentAddedHandler(const Comment::Ptr& comment)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("CommentAdded");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::CommentAdded));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'CommentAdded'.");
+
+ Dictionary::Ptr result = new Dictionary({
+ { "type", "CommentAdded" },
+ { "timestamp", Utility::GetTime() },
+ { "comment", Serialize(comment, FAConfig | FAState) }
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::CommentRemovedHandler(const Comment::Ptr& comment)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("CommentRemoved");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::CommentRemoved));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'CommentRemoved'.");
+
+ Dictionary::Ptr result = new Dictionary({
+ { "type", "CommentRemoved" },
+ { "timestamp", Utility::GetTime() },
+ { "comment", Serialize(comment, FAConfig | FAState) }
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::DowntimeAddedHandler(const Downtime::Ptr& downtime)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("DowntimeAdded");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::DowntimeAdded));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'DowntimeAdded'.");
+
+ Dictionary::Ptr result = new Dictionary({
+ { "type", "DowntimeAdded" },
+ { "timestamp", Utility::GetTime() },
+ { "downtime", Serialize(downtime, FAConfig | FAState) }
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::DowntimeRemovedHandler(const Downtime::Ptr& downtime)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("DowntimeRemoved");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::DowntimeRemoved));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'DowntimeRemoved'.");
+
+ Dictionary::Ptr result = new Dictionary({
+ { "type", "DowntimeRemoved" },
+ { "timestamp", Utility::GetTime() },
+ { "downtime", Serialize(downtime, FAConfig | FAState) }
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::DowntimeStartedHandler(const Downtime::Ptr& downtime)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("DowntimeStarted");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::DowntimeStarted));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'DowntimeStarted'.");
+
+ Dictionary::Ptr result = new Dictionary({
+ { "type", "DowntimeStarted" },
+ { "timestamp", Utility::GetTime() },
+ { "downtime", Serialize(downtime, FAConfig | FAState) }
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::DowntimeTriggeredHandler(const Downtime::Ptr& downtime)
+{
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType("DowntimeTriggered");
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(EventType::DowntimeTriggered));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents", "Processing event type 'DowntimeTriggered'.");
+
+ Dictionary::Ptr result = new Dictionary({
+ { "type", "DowntimeTriggered" },
+ { "timestamp", Utility::GetTime() },
+ { "downtime", Serialize(downtime, FAConfig | FAState) }
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
+
+void ApiEvents::OnActiveChangedHandler(const ConfigObject::Ptr& object, const Value&)
+{
+ if (object->IsActive()) {
+ ApiEvents::SendObjectChangeEvent(object, EventType::ObjectCreated, "ObjectCreated");
+ } else if (!object->IsActive() && !object->GetExtension("ConfigObjectDeleted").IsEmpty()) {
+ ApiEvents::SendObjectChangeEvent(object, EventType::ObjectDeleted, "ObjectDeleted");
+ }
+}
+
+void ApiEvents::OnVersionChangedHandler(const ConfigObject::Ptr& object, const Value&)
+{
+ ApiEvents::SendObjectChangeEvent(object, EventType::ObjectModified, "ObjectModified");
+}
+
+void ApiEvents::SendObjectChangeEvent(const ConfigObject::Ptr& object, const EventType& eventType, const String& eventQueue) {
+ std::vector<EventQueue::Ptr> queues = EventQueue::GetQueuesForType(eventQueue);
+ auto inboxes (EventsRouter::GetInstance().GetInboxes(eventType));
+
+ if (queues.empty() && !inboxes)
+ return;
+
+ Log(LogDebug, "ApiEvents") << "Processing event type '" + eventQueue + "'.";
+
+ Dictionary::Ptr result = new Dictionary ({
+ {"type", eventQueue},
+ {"timestamp", Utility::GetTime()},
+ {"object_type", object->GetReflectionType()->GetName()},
+ {"object_name", object->GetName()},
+ });
+
+ for (const EventQueue::Ptr& queue : queues) {
+ queue->ProcessEvent(result);
+ }
+
+ inboxes.Push(std::move(result));
+}
diff --git a/lib/icinga/apievents.hpp b/lib/icinga/apievents.hpp
new file mode 100644
index 0000000..07d5c60
--- /dev/null
+++ b/lib/icinga/apievents.hpp
@@ -0,0 +1,51 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef APIEVENTS_H
+#define APIEVENTS_H
+
+#include "remote/eventqueue.hpp"
+#include "icinga/checkable.hpp"
+#include "icinga/host.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup icinga
+ */
+class ApiEvents
+{
+public:
+ static void StaticInitialize();
+
+ static void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin);
+ static void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type, const MessageOrigin::Ptr& origin);
+
+
+ static void NotificationSentToAllUsersHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable,
+ const std::set<User::Ptr>& users, NotificationType type, const CheckResult::Ptr& cr, const String& author,
+ const String& text, const MessageOrigin::Ptr& origin);
+
+ static void FlappingChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+
+ static void AcknowledgementSetHandler(const Checkable::Ptr& checkable,
+ const String& author, const String& comment, AcknowledgementType type,
+ bool notify, bool persistent, double, double expiry, const MessageOrigin::Ptr& origin);
+ static void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double, const MessageOrigin::Ptr& origin);
+
+ static void CommentAddedHandler(const Comment::Ptr& comment);
+ static void CommentRemovedHandler(const Comment::Ptr& comment);
+
+ static void DowntimeAddedHandler(const Downtime::Ptr& downtime);
+ static void DowntimeRemovedHandler(const Downtime::Ptr& downtime);
+ static void DowntimeStartedHandler(const Downtime::Ptr& downtime);
+ static void DowntimeTriggeredHandler(const Downtime::Ptr& downtime);
+
+ static void OnActiveChangedHandler(const ConfigObject::Ptr& object, const Value&);
+ static void OnVersionChangedHandler(const ConfigObject::Ptr& object, const Value&);
+ static void SendObjectChangeEvent(const ConfigObject::Ptr& object, const EventType& eventType, const String& eventQueue);
+};
+
+}
+
+#endif /* APIEVENTS_H */
diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp
new file mode 100644
index 0000000..efa9477
--- /dev/null
+++ b/lib/icinga/checkable-check.cpp
@@ -0,0 +1,709 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/service.hpp"
+#include "icinga/host.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/cib.hpp"
+#include "icinga/clusterevents.hpp"
+#include "remote/messageorigin.hpp"
+#include "remote/apilistener.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include "base/context.hpp"
+
+using namespace icinga;
+
+boost::signals2::signal<void (const Checkable::Ptr&, const CheckResult::Ptr&, const MessageOrigin::Ptr&)> Checkable::OnNewCheckResult;
+boost::signals2::signal<void (const Checkable::Ptr&, const CheckResult::Ptr&, StateType, const MessageOrigin::Ptr&)> Checkable::OnStateChange;
+boost::signals2::signal<void (const Checkable::Ptr&, const CheckResult::Ptr&, std::set<Checkable::Ptr>, const MessageOrigin::Ptr&)> Checkable::OnReachabilityChanged;
+boost::signals2::signal<void (const Checkable::Ptr&, NotificationType, const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&)> Checkable::OnNotificationsRequested;
+boost::signals2::signal<void (const Checkable::Ptr&)> Checkable::OnNextCheckUpdated;
+
+Atomic<uint_fast64_t> Checkable::CurrentConcurrentChecks (0);
+
+std::mutex Checkable::m_StatsMutex;
+int Checkable::m_PendingChecks = 0;
+std::condition_variable Checkable::m_PendingChecksCV;
+
+CheckCommand::Ptr Checkable::GetCheckCommand() const
+{
+ return dynamic_pointer_cast<CheckCommand>(NavigateCheckCommandRaw());
+}
+
+TimePeriod::Ptr Checkable::GetCheckPeriod() const
+{
+ return TimePeriod::GetByName(GetCheckPeriodRaw());
+}
+
+void Checkable::SetSchedulingOffset(long offset)
+{
+ m_SchedulingOffset = offset;
+}
+
+long Checkable::GetSchedulingOffset()
+{
+ return m_SchedulingOffset;
+}
+
+void Checkable::UpdateNextCheck(const MessageOrigin::Ptr& origin)
+{
+ double interval;
+
+ if (GetStateType() == StateTypeSoft && GetLastCheckResult() != nullptr)
+ interval = GetRetryInterval();
+ else
+ interval = GetCheckInterval();
+
+ double now = Utility::GetTime();
+ double adj = 0;
+
+ if (interval > 1)
+ adj = fmod(now * 100 + GetSchedulingOffset(), interval * 100) / 100.0;
+
+ if (adj != 0.0)
+ adj = std::min(0.5 + fmod(GetSchedulingOffset(), interval * 5) / 100.0, adj);
+
+ double nextCheck = now - adj + interval;
+ double lastCheck = GetLastCheck();
+
+ Log(LogDebug, "Checkable")
+ << "Update checkable '" << GetName() << "' with check interval '" << GetCheckInterval()
+ << "' from last check time at " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", (lastCheck < 0 ? 0 : lastCheck))
+ << " (" << GetLastCheck() << ") to next check time at " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", nextCheck) << " (" << nextCheck << ").";
+
+ SetNextCheck(nextCheck, false, origin);
+}
+
+bool Checkable::HasBeenChecked() const
+{
+ return GetLastCheckResult() != nullptr;
+}
+
+double Checkable::GetLastCheck() const
+{
+ CheckResult::Ptr cr = GetLastCheckResult();
+ double schedule_end = -1;
+
+ if (cr)
+ schedule_end = cr->GetScheduleEnd();
+
+ return schedule_end;
+}
+
+Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin)
+{
+ using Result = Checkable::ProcessingResult;
+
+ {
+ ObjectLock olock(this);
+ m_CheckRunning = false;
+ }
+
+ if (!cr)
+ return Result::NoCheckResult;
+
+ double now = Utility::GetTime();
+
+ if (cr->GetScheduleStart() == 0)
+ cr->SetScheduleStart(now);
+
+ if (cr->GetScheduleEnd() == 0)
+ cr->SetScheduleEnd(now);
+
+ if (cr->GetExecutionStart() == 0)
+ cr->SetExecutionStart(now);
+
+ if (cr->GetExecutionEnd() == 0)
+ cr->SetExecutionEnd(now);
+
+ if (!origin || origin->IsLocal())
+ cr->SetSchedulingSource(IcingaApplication::GetInstance()->GetNodeName());
+
+ Endpoint::Ptr command_endpoint = GetCommandEndpoint();
+
+ if (cr->GetCheckSource().IsEmpty()) {
+ if ((!origin || origin->IsLocal()))
+ cr->SetCheckSource(IcingaApplication::GetInstance()->GetNodeName());
+
+ /* override check source if command_endpoint was defined */
+ if (command_endpoint && !GetExtension("agent_check"))
+ cr->SetCheckSource(command_endpoint->GetName());
+ }
+
+ /* agent checks go through the api */
+ if (command_endpoint && GetExtension("agent_check")) {
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (listener) {
+ /* send message back to its origin */
+ Dictionary::Ptr message = ClusterEvents::MakeCheckResultMessage(this, cr);
+ listener->SyncSendMessage(command_endpoint, message);
+ }
+
+ return Result::Ok;
+
+ }
+
+ if (!IsActive())
+ return Result::CheckableInactive;
+
+ bool reachable = IsReachable();
+ bool notification_reachable = IsReachable(DependencyNotification);
+
+ ObjectLock olock(this);
+
+ CheckResult::Ptr old_cr = GetLastCheckResult();
+ ServiceState old_state = GetStateRaw();
+ StateType old_stateType = GetStateType();
+ long old_attempt = GetCheckAttempt();
+ bool recovery = false;
+
+ /* When we have an check result already (not after fresh start),
+ * prevent to accept old check results and allow overrides for
+ * CRs happened in the future.
+ */
+ if (old_cr) {
+ double currentCRTimestamp = old_cr->GetExecutionStart();
+ double newCRTimestamp = cr->GetExecutionStart();
+
+ /* Our current timestamp may be from the future (wrong server time adjusted again). Allow overrides here. */
+ if (currentCRTimestamp > now) {
+ /* our current CR is from the future, let the new CR override it. */
+ Log(LogDebug, "Checkable")
+ << std::fixed << std::setprecision(6) << "Processing check result for checkable '" << GetName() << "' from "
+ << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newCRTimestamp) << " (" << newCRTimestamp
+ << "). Overriding since ours is from the future at "
+ << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", currentCRTimestamp) << " (" << currentCRTimestamp << ").";
+ } else {
+ /* Current timestamp is from the past, but the new timestamp is even more in the past. Skip it. */
+ if (newCRTimestamp < currentCRTimestamp) {
+ Log(LogDebug, "Checkable")
+ << std::fixed << std::setprecision(6) << "Skipping check result for checkable '" << GetName() << "' from "
+ << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newCRTimestamp) << " (" << newCRTimestamp
+ << "). It is in the past compared to ours at "
+ << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", currentCRTimestamp) << " (" << currentCRTimestamp << ").";
+ return Result::NewerCheckResultPresent;
+ }
+ }
+ }
+
+ /* The ExecuteCheck function already sets the old state, but we need to do it again
+ * in case this was a passive check result. */
+ SetLastStateRaw(old_state);
+ SetLastStateType(old_stateType);
+ SetLastReachable(reachable);
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(this);
+
+ CheckableType checkableType = CheckableHost;
+ if (service)
+ checkableType = CheckableService;
+
+ long attempt = 1;
+
+ std::set<Checkable::Ptr> children = GetChildren();
+
+ if (IsStateOK(cr->GetState())) {
+ SetStateType(StateTypeHard); // NOT-OK -> HARD OK
+
+ if (!IsStateOK(old_state))
+ recovery = true;
+
+ ResetNotificationNumbers();
+ SaveLastState(ServiceOK, cr->GetExecutionEnd());
+ } else {
+ /* OK -> NOT-OK change, first SOFT state. Reset attempt counter. */
+ if (IsStateOK(old_state)) {
+ SetStateType(StateTypeSoft);
+ attempt = 1;
+ }
+
+ /* SOFT state change, increase attempt counter. */
+ if (old_stateType == StateTypeSoft && !IsStateOK(old_state)) {
+ SetStateType(StateTypeSoft);
+ attempt = old_attempt + 1;
+ }
+
+ /* HARD state change (e.g. previously 2/3 and this next attempt). Reset attempt counter. */
+ if (attempt >= GetMaxCheckAttempts()) {
+ SetStateType(StateTypeHard);
+ attempt = 1;
+ }
+
+ if (!IsStateOK(cr->GetState())) {
+ SaveLastState(cr->GetState(), cr->GetExecutionEnd());
+ }
+ }
+
+ if (!reachable)
+ SetLastStateUnreachable(cr->GetExecutionEnd());
+
+ SetCheckAttempt(attempt);
+
+ ServiceState new_state = cr->GetState();
+ SetStateRaw(new_state);
+
+ bool stateChange;
+
+ /* Exception on state change calculation for hosts. */
+ if (checkableType == CheckableService)
+ stateChange = (old_state != new_state);
+ else
+ stateChange = (Host::CalculateState(old_state) != Host::CalculateState(new_state));
+
+ /* Store the current last state change for the next iteration. */
+ SetPreviousStateChange(GetLastStateChange());
+
+ if (stateChange) {
+ SetLastStateChange(cr->GetExecutionEnd());
+
+ /* remove acknowledgements */
+ if (GetAcknowledgement() == AcknowledgementNormal ||
+ (GetAcknowledgement() == AcknowledgementSticky && IsStateOK(new_state))) {
+ ClearAcknowledgement("");
+ }
+ }
+
+ bool remove_acknowledgement_comments = false;
+
+ if (GetAcknowledgement() == AcknowledgementNone)
+ remove_acknowledgement_comments = true;
+
+ bool hardChange = (GetStateType() == StateTypeHard && old_stateType == StateTypeSoft);
+
+ if (stateChange && old_stateType == StateTypeHard && GetStateType() == StateTypeHard)
+ hardChange = true;
+
+ bool is_volatile = GetVolatile();
+
+ if (hardChange || is_volatile) {
+ SetLastHardStateRaw(new_state);
+ SetLastHardStateChange(cr->GetExecutionEnd());
+ SetLastHardStatesRaw(GetLastHardStatesRaw() / 100u + new_state * 100u);
+ }
+
+ if (stateChange) {
+ SetLastSoftStatesRaw(GetLastSoftStatesRaw() / 100u + new_state * 100u);
+ }
+
+ cr->SetPreviousHardState(ServiceState(GetLastHardStatesRaw() % 100u));
+
+ if (!IsStateOK(new_state))
+ TriggerDowntimes(cr->GetExecutionEnd());
+
+ /* statistics for external tools */
+ Checkable::UpdateStatistics(cr, checkableType);
+
+ bool in_downtime = IsInDowntime();
+
+ bool send_notification = false;
+ bool suppress_notification = !notification_reachable || in_downtime || IsAcknowledged();
+
+ /* Send notifications whether when a hard state change occurred. */
+ if (hardChange && !(old_stateType == StateTypeSoft && IsStateOK(new_state)))
+ send_notification = true;
+ /* Or if the checkable is volatile and in a HARD state. */
+ else if (is_volatile && GetStateType() == StateTypeHard)
+ send_notification = true;
+
+ if (IsStateOK(old_state) && old_stateType == StateTypeSoft)
+ send_notification = false; /* Don't send notifications for SOFT-OK -> HARD-OK. */
+
+ if (is_volatile && IsStateOK(old_state) && IsStateOK(new_state))
+ send_notification = false; /* Don't send notifications for volatile OK -> OK changes. */
+
+ olock.Unlock();
+
+ if (remove_acknowledgement_comments)
+ RemoveAckComments(String(), cr->GetExecutionEnd());
+
+ Dictionary::Ptr vars_after = new Dictionary({
+ { "state", new_state },
+ { "state_type", GetStateType() },
+ { "attempt", GetCheckAttempt() },
+ { "reachable", reachable }
+ });
+
+ if (old_cr)
+ cr->SetVarsBefore(old_cr->GetVarsAfter());
+
+ cr->SetVarsAfter(vars_after);
+
+ olock.Lock();
+
+ if (service) {
+ SetLastCheckResult(cr);
+ } else {
+ bool wasProblem = GetProblem();
+
+ SetLastCheckResult(cr);
+
+ if (GetProblem() != wasProblem) {
+ auto services = host->GetServices();
+ olock.Unlock();
+ for (auto& service : services) {
+ Service::OnHostProblemChanged(service, cr, origin);
+ }
+ olock.Lock();
+ }
+ }
+
+ bool was_flapping = IsFlapping();
+
+ UpdateFlappingStatus(cr->GetState());
+
+ bool is_flapping = IsFlapping();
+
+ if (cr->GetActive()) {
+ UpdateNextCheck(origin);
+ } else {
+ /* Reschedule the next check for external passive check results. The side effect of
+ * this is that for as long as we receive results for a service we
+ * won't execute any active checks. */
+ double offset;
+ double ttl = cr->GetTtl();
+
+ if (ttl > 0)
+ offset = ttl;
+ else
+ offset = GetCheckInterval();
+
+ SetNextCheck(Utility::GetTime() + offset, false, origin);
+ }
+
+ olock.Unlock();
+
+#ifdef I2_DEBUG /* I2_DEBUG */
+ Log(LogDebug, "Checkable")
+ << "Flapping: Checkable " << GetName()
+ << " was: " << was_flapping
+ << " is: " << is_flapping
+ << " threshold low: " << GetFlappingThresholdLow()
+ << " threshold high: " << GetFlappingThresholdHigh()
+ << "% current: " << GetFlappingCurrent() << "%.";
+#endif /* I2_DEBUG */
+
+ if (recovery) {
+ for (auto& child : children) {
+ if (child->GetProblem() && child->GetEnableActiveChecks()) {
+ auto nextCheck (now + Utility::Random() % 60);
+
+ ObjectLock oLock (child);
+
+ if (nextCheck < child->GetNextCheck()) {
+ child->SetNextCheck(nextCheck);
+ }
+ }
+ }
+ }
+
+ if (stateChange) {
+ /* reschedule direct parents */
+ for (const Checkable::Ptr& parent : GetParents()) {
+ if (parent.get() == this)
+ continue;
+
+ if (!parent->GetEnableActiveChecks())
+ continue;
+
+ if (parent->GetNextCheck() >= now + parent->GetRetryInterval()) {
+ ObjectLock olock(parent);
+ parent->SetNextCheck(now);
+ }
+ }
+ }
+
+ OnNewCheckResult(this, cr, origin);
+
+ /* signal status updates to for example db_ido */
+ OnStateChanged(this);
+
+ String old_state_str = (service ? Service::StateToString(old_state) : Host::StateToString(Host::CalculateState(old_state)));
+ String new_state_str = (service ? Service::StateToString(new_state) : Host::StateToString(Host::CalculateState(new_state)));
+
+ /* Whether a hard state change or a volatile state change except OK -> OK happened. */
+ if (hardChange || (is_volatile && !(IsStateOK(old_state) && IsStateOK(new_state)))) {
+ OnStateChange(this, cr, StateTypeHard, origin);
+ Log(LogNotice, "Checkable")
+ << "State Change: Checkable '" << GetName() << "' hard state change from " << old_state_str << " to " << new_state_str << " detected." << (is_volatile ? " Checkable is volatile." : "");
+ }
+ /* Whether a state change happened or the state type is SOFT (must be logged too). */
+ else if (stateChange || GetStateType() == StateTypeSoft) {
+ OnStateChange(this, cr, StateTypeSoft, origin);
+ Log(LogNotice, "Checkable")
+ << "State Change: Checkable '" << GetName() << "' soft state change from " << old_state_str << " to " << new_state_str << " detected.";
+ }
+
+ if (GetStateType() == StateTypeSoft || hardChange || recovery ||
+ (is_volatile && !(IsStateOK(old_state) && IsStateOK(new_state))))
+ ExecuteEventHandler();
+
+ int suppressed_types = 0;
+
+ /* Flapping start/end notifications */
+ if (!was_flapping && is_flapping) {
+ /* FlappingStart notifications happen on state changes, not in downtimes */
+ if (!IsPaused()) {
+ if (in_downtime) {
+ suppressed_types |= NotificationFlappingStart;
+ } else {
+ OnNotificationsRequested(this, NotificationFlappingStart, cr, "", "", nullptr);
+ }
+ }
+
+ Log(LogNotice, "Checkable")
+ << "Flapping Start: Checkable '" << GetName() << "' started flapping (Current flapping value "
+ << GetFlappingCurrent() << "% > high threshold " << GetFlappingThresholdHigh() << "%).";
+
+ NotifyFlapping(origin);
+ } else if (was_flapping && !is_flapping) {
+ /* FlappingEnd notifications are independent from state changes, must not happen in downtine */
+ if (!IsPaused()) {
+ if (in_downtime) {
+ suppressed_types |= NotificationFlappingEnd;
+ } else {
+ OnNotificationsRequested(this, NotificationFlappingEnd, cr, "", "", nullptr);
+ }
+ }
+
+ Log(LogNotice, "Checkable")
+ << "Flapping Stop: Checkable '" << GetName() << "' stopped flapping (Current flapping value "
+ << GetFlappingCurrent() << "% < low threshold " << GetFlappingThresholdLow() << "%).";
+
+ NotifyFlapping(origin);
+ }
+
+ if (send_notification && !is_flapping) {
+ if (!IsPaused()) {
+ /* If there are still some pending suppressed state notification, keep the suppression until these are
+ * handled by Checkable::FireSuppressedNotifications().
+ */
+ bool pending = GetSuppressedNotifications() & (NotificationRecovery|NotificationProblem);
+
+ if (suppress_notification || pending) {
+ suppressed_types |= (recovery ? NotificationRecovery : NotificationProblem);
+ } else {
+ OnNotificationsRequested(this, recovery ? NotificationRecovery : NotificationProblem, cr, "", "", nullptr);
+ }
+ }
+ }
+
+ if (suppressed_types) {
+ /* If some notifications were suppressed, but just because of e.g. a downtime,
+ * stash them into a notification types bitmask for maybe re-sending later.
+ */
+
+ ObjectLock olock (this);
+ int suppressed_types_before (GetSuppressedNotifications());
+ int suppressed_types_after (suppressed_types_before | suppressed_types);
+
+ const int conflict = NotificationFlappingStart | NotificationFlappingEnd;
+ if ((suppressed_types_after & conflict) == conflict) {
+ /* Flapping start and end cancel out each other. */
+ suppressed_types_after &= ~conflict;
+ }
+
+ const int stateNotifications = NotificationRecovery | NotificationProblem;
+ if (!(suppressed_types_before & stateNotifications) && (suppressed_types & stateNotifications)) {
+ /* A state-related notification is suppressed for the first time, store the previous state. When
+ * notifications are no longer suppressed, this can be compared with the current state to determine
+ * if a notification must be sent. This is done differently compared to flapping notifications just above
+ * as for state notifications, problem and recovery don't always cancel each other. For example,
+ * WARNING -> OK -> CRITICAL generates both types once, but there should still be a notification.
+ */
+ SetStateBeforeSuppression(old_stateType == StateTypeHard ? old_state : ServiceOK);
+ }
+
+ if (suppressed_types_after != suppressed_types_before) {
+ SetSuppressedNotifications(suppressed_types_after);
+ }
+ }
+
+ /* update reachability for child objects */
+ if ((stateChange || hardChange) && !children.empty())
+ OnReachabilityChanged(this, cr, children, origin);
+
+ return Result::Ok;
+}
+
+void Checkable::ExecuteRemoteCheck(const Dictionary::Ptr& resolvedMacros)
+{
+ CONTEXT("Executing remote check for object '" << GetName() << "'");
+
+ double scheduled_start = GetNextCheck();
+ double before_check = Utility::GetTime();
+
+ CheckResult::Ptr cr = new CheckResult();
+ cr->SetScheduleStart(scheduled_start);
+ cr->SetExecutionStart(before_check);
+
+ GetCheckCommand()->Execute(this, cr, resolvedMacros, true);
+}
+
+void Checkable::ExecuteCheck()
+{
+ CONTEXT("Executing check for object '" << GetName() << "'");
+
+ /* keep track of scheduling info in case the check type doesn't provide its own information */
+ double scheduled_start = GetNextCheck();
+ double before_check = Utility::GetTime();
+
+ SetLastCheckStarted(Utility::GetTime());
+
+ /* This calls SetNextCheck() which updates the CheckerComponent's idle/pending
+ * queues and ensures that checks are not fired multiple times. ProcessCheckResult()
+ * is called too late. See #6421.
+ */
+ UpdateNextCheck();
+
+ bool reachable = IsReachable();
+
+ {
+ ObjectLock olock(this);
+
+ /* don't run another check if there is one pending */
+ if (m_CheckRunning)
+ return;
+
+ m_CheckRunning = true;
+
+ SetLastStateRaw(GetStateRaw());
+ SetLastStateType(GetLastStateType());
+ SetLastReachable(reachable);
+ }
+
+ CheckResult::Ptr cr = new CheckResult();
+
+ cr->SetScheduleStart(scheduled_start);
+ cr->SetExecutionStart(before_check);
+
+ Endpoint::Ptr endpoint = GetCommandEndpoint();
+ bool local = !endpoint || endpoint == Endpoint::GetLocalEndpoint();
+
+ if (local) {
+ GetCheckCommand()->Execute(this, cr, nullptr, false);
+ } else {
+ Dictionary::Ptr macros = new Dictionary();
+ GetCheckCommand()->Execute(this, cr, macros, false);
+
+ if (endpoint->GetConnected()) {
+ /* perform check on remote endpoint */
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::ExecuteCommand");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(this);
+
+ Dictionary::Ptr params = new Dictionary();
+ message->Set("params", params);
+ params->Set("command_type", "check_command");
+ params->Set("command", GetCheckCommand()->GetName());
+ params->Set("host", host->GetName());
+
+ if (service)
+ params->Set("service", service->GetShortName());
+
+ /*
+ * If the host/service object specifies the 'check_timeout' attribute,
+ * forward this to the remote endpoint to limit the command execution time.
+ */
+ if (!GetCheckTimeout().IsEmpty())
+ params->Set("check_timeout", GetCheckTimeout());
+
+ params->Set("macros", macros);
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (listener)
+ listener->SyncSendMessage(endpoint, message);
+
+ /* Re-schedule the check so we don't run it again until after we've received
+ * a check result from the remote instance. The check will be re-scheduled
+ * using the proper check interval once we've received a check result.
+ */
+ SetNextCheck(Utility::GetTime() + GetCheckCommand()->GetTimeout() + 30);
+
+ /*
+ * Let the user know that there was a problem with the check if
+ * 1) The endpoint is not syncing (replay log, etc.)
+ * 2) Outside of the cold startup window (5min)
+ */
+ } else if (!endpoint->GetSyncing() && Application::GetInstance()->GetStartTime() < Utility::GetTime() - 300) {
+ /* fail to perform check on unconnected endpoint */
+ cr->SetState(ServiceUnknown);
+
+ String output = "Remote Icinga instance '" + endpoint->GetName() + "' is not connected to ";
+
+ Endpoint::Ptr localEndpoint = Endpoint::GetLocalEndpoint();
+
+ if (localEndpoint)
+ output += "'" + localEndpoint->GetName() + "'";
+ else
+ output += "this instance";
+
+ cr->SetOutput(output);
+
+ ProcessCheckResult(cr);
+ }
+
+ {
+ ObjectLock olock(this);
+ m_CheckRunning = false;
+ }
+ }
+}
+
+void Checkable::UpdateStatistics(const CheckResult::Ptr& cr, CheckableType type)
+{
+ time_t ts = cr->GetScheduleEnd();
+
+ if (type == CheckableHost) {
+ if (cr->GetActive())
+ CIB::UpdateActiveHostChecksStatistics(ts, 1);
+ else
+ CIB::UpdatePassiveHostChecksStatistics(ts, 1);
+ } else if (type == CheckableService) {
+ if (cr->GetActive())
+ CIB::UpdateActiveServiceChecksStatistics(ts, 1);
+ else
+ CIB::UpdatePassiveServiceChecksStatistics(ts, 1);
+ } else {
+ Log(LogWarning, "Checkable", "Unknown checkable type for statistic update.");
+ }
+}
+
+void Checkable::IncreasePendingChecks()
+{
+ std::unique_lock<std::mutex> lock(m_StatsMutex);
+ m_PendingChecks++;
+}
+
+void Checkable::DecreasePendingChecks()
+{
+ std::unique_lock<std::mutex> lock(m_StatsMutex);
+ m_PendingChecks--;
+ m_PendingChecksCV.notify_one();
+}
+
+int Checkable::GetPendingChecks()
+{
+ std::unique_lock<std::mutex> lock(m_StatsMutex);
+ return m_PendingChecks;
+}
+
+void Checkable::AquirePendingCheckSlot(int maxPendingChecks)
+{
+ std::unique_lock<std::mutex> lock(m_StatsMutex);
+ while (m_PendingChecks >= maxPendingChecks)
+ m_PendingChecksCV.wait(lock);
+
+ m_PendingChecks++;
+}
diff --git a/lib/icinga/checkable-comment.cpp b/lib/icinga/checkable-comment.cpp
new file mode 100644
index 0000000..71cfac6
--- /dev/null
+++ b/lib/icinga/checkable-comment.cpp
@@ -0,0 +1,75 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/service.hpp"
+#include "remote/configobjectutility.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/timer.hpp"
+#include "base/utility.hpp"
+#include "base/logger.hpp"
+#include <utility>
+
+using namespace icinga;
+
+
+void Checkable::RemoveAllComments()
+{
+ for (const Comment::Ptr& comment : GetComments()) {
+ Comment::RemoveComment(comment->GetName());
+ }
+}
+
+void Checkable::RemoveAckComments(const String& removedBy, double createdBefore)
+{
+ for (const Comment::Ptr& comment : GetComments()) {
+ if (comment->GetEntryType() == CommentAcknowledgement) {
+ /* Do not remove persistent comments from an acknowledgement */
+ if (comment->GetPersistent()) {
+ continue;
+ }
+
+ if (comment->GetEntryTime() > createdBefore) {
+ continue;
+ }
+
+ {
+ ObjectLock oLock (comment);
+ comment->SetRemovedBy(removedBy);
+ }
+
+ Comment::RemoveComment(comment->GetName());
+ }
+ }
+}
+
+std::set<Comment::Ptr> Checkable::GetComments() const
+{
+ std::unique_lock<std::mutex> lock(m_CommentMutex);
+ return m_Comments;
+}
+
+Comment::Ptr Checkable::GetLastComment() const
+{
+ std::unique_lock<std::mutex> lock (m_CommentMutex);
+ Comment::Ptr lastComment;
+
+ for (auto& comment : m_Comments) {
+ if (!lastComment || comment->GetEntryTime() > lastComment->GetEntryTime()) {
+ lastComment = comment;
+ }
+ }
+
+ return lastComment;
+}
+
+void Checkable::RegisterComment(const Comment::Ptr& comment)
+{
+ std::unique_lock<std::mutex> lock(m_CommentMutex);
+ m_Comments.insert(comment);
+}
+
+void Checkable::UnregisterComment(const Comment::Ptr& comment)
+{
+ std::unique_lock<std::mutex> lock(m_CommentMutex);
+ m_Comments.erase(comment);
+}
diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp
new file mode 100644
index 0000000..58d6b57
--- /dev/null
+++ b/lib/icinga/checkable-dependency.cpp
@@ -0,0 +1,176 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/service.hpp"
+#include "icinga/dependency.hpp"
+#include "base/logger.hpp"
+#include <unordered_map>
+
+using namespace icinga;
+
+void Checkable::AddDependency(const Dependency::Ptr& dep)
+{
+ std::unique_lock<std::mutex> lock(m_DependencyMutex);
+ m_Dependencies.insert(dep);
+}
+
+void Checkable::RemoveDependency(const Dependency::Ptr& dep)
+{
+ std::unique_lock<std::mutex> lock(m_DependencyMutex);
+ m_Dependencies.erase(dep);
+}
+
+std::vector<Dependency::Ptr> Checkable::GetDependencies() const
+{
+ std::unique_lock<std::mutex> lock(m_DependencyMutex);
+ return std::vector<Dependency::Ptr>(m_Dependencies.begin(), m_Dependencies.end());
+}
+
+void Checkable::AddReverseDependency(const Dependency::Ptr& dep)
+{
+ std::unique_lock<std::mutex> lock(m_DependencyMutex);
+ m_ReverseDependencies.insert(dep);
+}
+
+void Checkable::RemoveReverseDependency(const Dependency::Ptr& dep)
+{
+ std::unique_lock<std::mutex> lock(m_DependencyMutex);
+ m_ReverseDependencies.erase(dep);
+}
+
+std::vector<Dependency::Ptr> Checkable::GetReverseDependencies() const
+{
+ std::unique_lock<std::mutex> lock(m_DependencyMutex);
+ return std::vector<Dependency::Ptr>(m_ReverseDependencies.begin(), m_ReverseDependencies.end());
+}
+
+bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency, int rstack) const
+{
+ /* Anything greater than 256 causes recursion bus errors. */
+ int limit = 256;
+
+ if (rstack > limit) {
+ Log(LogWarning, "Checkable")
+ << "Too many nested dependencies (>" << limit << ") for checkable '" << GetName() << "': Dependency failed.";
+
+ return false;
+ }
+
+ for (const Checkable::Ptr& checkable : GetParents()) {
+ if (!checkable->IsReachable(dt, failedDependency, rstack + 1))
+ return false;
+ }
+
+ /* implicit dependency on host if this is a service */
+ const auto *service = dynamic_cast<const Service *>(this);
+ if (service && (dt == DependencyState || dt == DependencyNotification)) {
+ Host::Ptr host = service->GetHost();
+
+ if (host && host->GetState() != HostUp && host->GetStateType() == StateTypeHard) {
+ if (failedDependency)
+ *failedDependency = nullptr;
+
+ return false;
+ }
+ }
+
+ auto deps = GetDependencies();
+
+ std::unordered_map<std::string, Dependency::Ptr> violated; // key: redundancy group, value: nullptr if satisfied, violating dependency otherwise
+
+ for (const Dependency::Ptr& dep : deps) {
+ std::string redundancy_group = dep->GetRedundancyGroup();
+
+ if (!dep->IsAvailable(dt)) {
+ if (redundancy_group.empty()) {
+ Log(LogDebug, "Checkable")
+ << "Non-redundant dependency '" << dep->GetName() << "' failed for checkable '" << GetName() << "': Marking as unreachable.";
+
+ if (failedDependency)
+ *failedDependency = dep;
+
+ return false;
+ }
+
+ // tentatively mark this dependency group as failed unless it is already marked;
+ // so it either passed before (don't overwrite) or already failed (so don't care)
+ // note that std::unordered_map::insert() will not overwrite an existing entry
+ violated.insert(std::make_pair(redundancy_group, dep));
+ } else if (!redundancy_group.empty()) {
+ violated[redundancy_group] = nullptr;
+ }
+ }
+
+ auto violator = std::find_if(violated.begin(), violated.end(), [](auto& v) { return v.second != nullptr; });
+ if (violator != violated.end()) {
+ Log(LogDebug, "Checkable")
+ << "All dependencies in redundancy group '" << violator->first << "' have failed for checkable '" << GetName() << "': Marking as unreachable.";
+
+ if (failedDependency)
+ *failedDependency = violator->second;
+
+ return false;
+ }
+
+ if (failedDependency)
+ *failedDependency = nullptr;
+
+ return true;
+}
+
+std::set<Checkable::Ptr> Checkable::GetParents() const
+{
+ std::set<Checkable::Ptr> parents;
+
+ for (const Dependency::Ptr& dep : GetDependencies()) {
+ Checkable::Ptr parent = dep->GetParent();
+
+ if (parent && parent.get() != this)
+ parents.insert(parent);
+ }
+
+ return parents;
+}
+
+std::set<Checkable::Ptr> Checkable::GetChildren() const
+{
+ std::set<Checkable::Ptr> parents;
+
+ for (const Dependency::Ptr& dep : GetReverseDependencies()) {
+ Checkable::Ptr service = dep->GetChild();
+
+ if (service && service.get() != this)
+ parents.insert(service);
+ }
+
+ return parents;
+}
+
+std::set<Checkable::Ptr> Checkable::GetAllChildren() const
+{
+ std::set<Checkable::Ptr> children = GetChildren();
+
+ GetAllChildrenInternal(children, 0);
+
+ return children;
+}
+
+void Checkable::GetAllChildrenInternal(std::set<Checkable::Ptr>& children, int level) const
+{
+ if (level > 32)
+ return;
+
+ std::set<Checkable::Ptr> localChildren;
+
+ for (const Checkable::Ptr& checkable : children) {
+ std::set<Checkable::Ptr> cChildren = checkable->GetChildren();
+
+ if (!cChildren.empty()) {
+ GetAllChildrenInternal(cChildren, level + 1);
+ localChildren.insert(cChildren.begin(), cChildren.end());
+ }
+
+ localChildren.insert(checkable);
+ }
+
+ children.insert(localChildren.begin(), localChildren.end());
+}
diff --git a/lib/icinga/checkable-downtime.cpp b/lib/icinga/checkable-downtime.cpp
new file mode 100644
index 0000000..d96003d
--- /dev/null
+++ b/lib/icinga/checkable-downtime.cpp
@@ -0,0 +1,64 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/service.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/utility.hpp"
+#include "base/convert.hpp"
+
+using namespace icinga;
+
+void Checkable::RemoveAllDowntimes()
+{
+ for (const Downtime::Ptr& downtime : GetDowntimes()) {
+ Downtime::RemoveDowntime(downtime->GetName(), true, true, true);
+ }
+}
+
+void Checkable::TriggerDowntimes(double triggerTime)
+{
+ for (const Downtime::Ptr& downtime : GetDowntimes()) {
+ downtime->TriggerDowntime(triggerTime);
+ }
+}
+
+bool Checkable::IsInDowntime() const
+{
+ for (const Downtime::Ptr& downtime : GetDowntimes()) {
+ if (downtime->IsInEffect())
+ return true;
+ }
+
+ return false;
+}
+
+int Checkable::GetDowntimeDepth() const
+{
+ int downtime_depth = 0;
+
+ for (const Downtime::Ptr& downtime : GetDowntimes()) {
+ if (downtime->IsInEffect())
+ downtime_depth++;
+ }
+
+ return downtime_depth;
+}
+
+std::set<Downtime::Ptr> Checkable::GetDowntimes() const
+{
+ std::unique_lock<std::mutex> lock(m_DowntimeMutex);
+ return m_Downtimes;
+}
+
+void Checkable::RegisterDowntime(const Downtime::Ptr& downtime)
+{
+ std::unique_lock<std::mutex> lock(m_DowntimeMutex);
+ m_Downtimes.insert(downtime);
+}
+
+void Checkable::UnregisterDowntime(const Downtime::Ptr& downtime)
+{
+ std::unique_lock<std::mutex> lock(m_DowntimeMutex);
+ m_Downtimes.erase(downtime);
+}
diff --git a/lib/icinga/checkable-event.cpp b/lib/icinga/checkable-event.cpp
new file mode 100644
index 0000000..fb315d9
--- /dev/null
+++ b/lib/icinga/checkable-event.cpp
@@ -0,0 +1,81 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/service.hpp"
+#include "remote/apilistener.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+
+using namespace icinga;
+
+boost::signals2::signal<void (const Checkable::Ptr&)> Checkable::OnEventCommandExecuted;
+
+EventCommand::Ptr Checkable::GetEventCommand() const
+{
+ return EventCommand::GetByName(GetEventCommandRaw());
+}
+
+void Checkable::ExecuteEventHandler(const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros)
+{
+ CONTEXT("Executing event handler for object '" << GetName() << "'");
+
+ if (!IcingaApplication::GetInstance()->GetEnableEventHandlers() || !GetEnableEventHandler())
+ return;
+
+ /* HA enabled zones. */
+ if (IsActive() && IsPaused()) {
+ Log(LogNotice, "Checkable")
+ << "Skipping event handler for HA-paused checkable '" << GetName() << "'";
+ return;
+ }
+
+ EventCommand::Ptr ec = GetEventCommand();
+
+ if (!ec)
+ return;
+
+ Log(LogNotice, "Checkable")
+ << "Executing event handler '" << ec->GetName() << "' for checkable '" << GetName() << "'";
+
+ Dictionary::Ptr macros;
+ Endpoint::Ptr endpoint = GetCommandEndpoint();
+
+ if (endpoint && !useResolvedMacros)
+ macros = new Dictionary();
+ else
+ macros = resolvedMacros;
+
+ ec->Execute(this, macros, useResolvedMacros);
+
+ if (endpoint && !GetExtension("agent_check")) {
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::ExecuteCommand");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(this);
+
+ Dictionary::Ptr params = new Dictionary();
+ message->Set("params", params);
+ params->Set("command_type", "event_command");
+ params->Set("command", GetEventCommand()->GetName());
+ params->Set("host", host->GetName());
+
+ if (service)
+ params->Set("service", service->GetShortName());
+
+ params->Set("macros", macros);
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (listener)
+ listener->SyncSendMessage(endpoint, message);
+
+ return;
+ }
+
+ OnEventCommandExecuted(this);
+}
diff --git a/lib/icinga/checkable-flapping.cpp b/lib/icinga/checkable-flapping.cpp
new file mode 100644
index 0000000..e905e05
--- /dev/null
+++ b/lib/icinga/checkable-flapping.cpp
@@ -0,0 +1,114 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "base/utility.hpp"
+
+using namespace icinga;
+
+template<typename T>
+struct Bitset
+{
+public:
+ Bitset(T value)
+ : m_Data(value)
+ { }
+
+ void Modify(int index, bool bit)
+ {
+ if (bit)
+ m_Data |= 1 << index;
+ else
+ m_Data &= ~(1 << index);
+ }
+
+ bool Get(int index) const
+ {
+ return m_Data & (1 << index);
+ }
+
+ T GetValue() const
+ {
+ return m_Data;
+ }
+
+private:
+ T m_Data{0};
+};
+
+void Checkable::UpdateFlappingStatus(ServiceState newState)
+{
+ Bitset<unsigned long> stateChangeBuf = GetFlappingBuffer();
+ int oldestIndex = GetFlappingIndex();
+
+ ServiceState lastState = GetFlappingLastState();
+ bool stateChange = false;
+
+ int stateFilter = GetFlappingIgnoreStatesFilter();
+
+ /* Only count as state change if no state filter is set or the new state isn't filtered out */
+ if (stateFilter == -1 || !(ServiceStateToFlappingFilter(newState) & stateFilter)) {
+ stateChange = newState != lastState;
+ SetFlappingLastState(newState);
+ }
+
+ stateChangeBuf.Modify(oldestIndex, stateChange);
+ oldestIndex = (oldestIndex + 1) % 20;
+
+ double stateChanges = 0;
+
+ /* Iterate over our state array and compute a weighted total */
+ for (int i = 0; i < 20; i++) {
+ if (stateChangeBuf.Get((oldestIndex + i) % 20))
+ stateChanges += 0.8 + (0.02 * i);
+ }
+
+ double flappingValue = 100.0 * stateChanges / 20.0;
+
+ bool flapping;
+
+ if (GetFlapping())
+ flapping = flappingValue > GetFlappingThresholdLow();
+ else
+ flapping = flappingValue > GetFlappingThresholdHigh();
+
+ SetFlappingBuffer(stateChangeBuf.GetValue());
+ SetFlappingIndex(oldestIndex);
+ SetFlappingCurrent(flappingValue);
+
+ if (flapping != GetFlapping()) {
+ SetFlapping(flapping, true);
+
+ double ee = GetLastCheckResult()->GetExecutionEnd();
+
+ if (GetEnableFlapping() && IcingaApplication::GetInstance()->GetEnableFlapping()) {
+ OnFlappingChange(this, ee);
+ }
+
+ SetFlappingLastChange(ee);
+ }
+}
+
+bool Checkable::IsFlapping() const
+{
+ if (!GetEnableFlapping() || !IcingaApplication::GetInstance()->GetEnableFlapping())
+ return false;
+ else
+ return GetFlapping();
+}
+
+int Checkable::ServiceStateToFlappingFilter(ServiceState state)
+{
+ switch (state) {
+ case ServiceOK:
+ return StateFilterOK;
+ case ServiceWarning:
+ return StateFilterWarning;
+ case ServiceCritical:
+ return StateFilterCritical;
+ case ServiceUnknown:
+ return StateFilterUnknown;
+ default:
+ VERIFY(!"Invalid state type.");
+ }
+}
diff --git a/lib/icinga/checkable-notification.cpp b/lib/icinga/checkable-notification.cpp
new file mode 100644
index 0000000..79b5986
--- /dev/null
+++ b/lib/icinga/checkable-notification.cpp
@@ -0,0 +1,334 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/host.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/service.hpp"
+#include "base/dictionary.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include "base/context.hpp"
+#include "base/convert.hpp"
+#include "base/lazy-init.hpp"
+#include "remote/apilistener.hpp"
+
+using namespace icinga;
+
+boost::signals2::signal<void (const Notification::Ptr&, const Checkable::Ptr&, const std::set<User::Ptr>&,
+ const NotificationType&, const CheckResult::Ptr&, const String&, const String&,
+ const MessageOrigin::Ptr&)> Checkable::OnNotificationSentToAllUsers;
+boost::signals2::signal<void (const Notification::Ptr&, const Checkable::Ptr&, const User::Ptr&,
+ const NotificationType&, const CheckResult::Ptr&, const String&, const String&, const String&,
+ const MessageOrigin::Ptr&)> Checkable::OnNotificationSentToUser;
+
+void Checkable::ResetNotificationNumbers()
+{
+ for (const Notification::Ptr& notification : GetNotifications()) {
+ ObjectLock olock(notification);
+ notification->ResetNotificationNumber();
+ }
+}
+
+void Checkable::SendNotifications(NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text)
+{
+ String checkableName = GetName();
+
+ CONTEXT("Sending notifications for object '" << checkableName << "'");
+
+ bool force = GetForceNextNotification();
+
+ SetForceNextNotification(false);
+
+ if (!IcingaApplication::GetInstance()->GetEnableNotifications() || !GetEnableNotifications()) {
+ if (!force) {
+ Log(LogInformation, "Checkable")
+ << "Notifications are disabled for checkable '" << checkableName << "'.";
+ return;
+ }
+ }
+
+ std::set<Notification::Ptr> notifications = GetNotifications();
+
+ String notificationTypeName = Notification::NotificationTypeToString(type);
+
+ // Bail early if there are no notifications.
+ if (notifications.empty()) {
+ Log(LogNotice, "Checkable")
+ << "Skipping checkable '" << checkableName << "' which doesn't have any notification objects configured.";
+ return;
+ }
+
+ Log(LogInformation, "Checkable")
+ << "Checkable '" << checkableName << "' has " << notifications.size()
+ << " notification(s). Checking filters for type '" << notificationTypeName << "', sends will be logged.";
+
+ for (const Notification::Ptr& notification : notifications) {
+ // Re-send stashed notifications from cold startup.
+ if (ApiListener::UpdatedObjectAuthority()) {
+ try {
+ if (!notification->IsPaused()) {
+ auto stashedNotifications (notification->GetStashedNotifications());
+
+ if (stashedNotifications->GetLength()) {
+ Log(LogNotice, "Notification")
+ << "Notification '" << notification->GetName() << "': there are some stashed notifications. Stashing notification to preserve order.";
+
+ stashedNotifications->Add(new Dictionary({
+ {"notification_type", type},
+ {"cr", cr},
+ {"force", force},
+ {"reminder", false},
+ {"author", author},
+ {"text", text}
+ }));
+ } else {
+ notification->BeginExecuteNotification(type, cr, force, false, author, text);
+ }
+ } else {
+ Log(LogNotice, "Notification")
+ << "Notification '" << notification->GetName() << "': HA cluster active, this endpoint does not have the authority (paused=true). Skipping.";
+ }
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "Checkable")
+ << "Exception occurred during notification '" << notification->GetName() << "' for checkable '"
+ << GetName() << "': " << DiagnosticInformation(ex, false);
+ }
+ } else {
+ // Cold startup phase. Stash notification for later.
+ Log(LogNotice, "Notification")
+ << "Notification '" << notification->GetName() << "': object authority hasn't been updated, yet. Stashing notification.";
+
+ notification->GetStashedNotifications()->Add(new Dictionary({
+ {"notification_type", type},
+ {"cr", cr},
+ {"force", force},
+ {"reminder", false},
+ {"author", author},
+ {"text", text}
+ }));
+ }
+ }
+}
+
+std::set<Notification::Ptr> Checkable::GetNotifications() const
+{
+ std::unique_lock<std::mutex> lock(m_NotificationMutex);
+ return m_Notifications;
+}
+
+void Checkable::RegisterNotification(const Notification::Ptr& notification)
+{
+ std::unique_lock<std::mutex> lock(m_NotificationMutex);
+ m_Notifications.insert(notification);
+}
+
+void Checkable::UnregisterNotification(const Notification::Ptr& notification)
+{
+ std::unique_lock<std::mutex> lock(m_NotificationMutex);
+ m_Notifications.erase(notification);
+}
+
+void Checkable::FireSuppressedNotifications()
+{
+ if (!IsActive())
+ return;
+
+ if (IsPaused())
+ return;
+
+ if (!GetEnableNotifications())
+ return;
+
+ int suppressed_types (GetSuppressedNotifications());
+ if (!suppressed_types)
+ return;
+
+ int subtract = 0;
+
+ {
+ LazyInit<bool> wasLastParentRecoveryRecent ([this]() {
+ auto cr (GetLastCheckResult());
+
+ if (!cr) {
+ return true;
+ }
+
+ auto threshold (cr->GetExecutionStart());
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(this);
+
+ if (service) {
+ ObjectLock oLock (host);
+
+ if (!host->GetProblem() && host->GetLastStateChange() >= threshold) {
+ return true;
+ }
+ }
+
+ for (auto& dep : GetDependencies()) {
+ auto parent (dep->GetParent());
+ ObjectLock oLock (parent);
+
+ if (!parent->GetProblem() && parent->GetLastStateChange() >= threshold) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+
+ if (suppressed_types & (NotificationProblem|NotificationRecovery)) {
+ CheckResult::Ptr cr = GetLastCheckResult();
+ NotificationType type = cr && IsStateOK(cr->GetState()) ? NotificationRecovery : NotificationProblem;
+ bool state_suppressed = NotificationReasonSuppressed(NotificationProblem) || NotificationReasonSuppressed(NotificationRecovery);
+
+ /* Only process (i.e. send or dismiss) suppressed state notifications if the following conditions are met:
+ *
+ * 1. State notifications are not suppressed at the moment. State notifications must only be removed from
+ * the suppressed notifications bitset after the reason for the suppression is gone as these bits are
+ * used as a marker for when to set the state_before_suppression attribute.
+ * 2. The checkable is in a hard state. Soft states represent a state where we are not certain yet about
+ * the actual state and wait with sending notifications. If we want to immediately send a notification,
+ * we might send a recovery notification for something that just started failing or a problem
+ * notification which might be for an intermittent problem that would have never received a
+ * notification if there was no suppression as it still was in a soft state. Both cases aren't ideal so
+ * better wait until we are certain.
+ * 3. The checkable isn't likely checked soon. For example, if a downtime ended, give the checkable a
+ * chance to recover afterwards before sending a notification.
+ * 4. No parent recovered recently. Similar to the previous condition, give the checkable a chance to
+ * recover after one of its dependencies recovered before sending a notification.
+ *
+ * If any of these conditions is not met, processing the suppressed notification is further delayed.
+ */
+ if (!state_suppressed && GetStateType() == StateTypeHard && !IsLikelyToBeCheckedSoon() && !wasLastParentRecoveryRecent.Get()) {
+ if (NotificationReasonApplies(type)) {
+ Checkable::OnNotificationsRequested(this, type, cr, "", "", nullptr);
+ }
+ subtract |= NotificationRecovery|NotificationProblem;
+ }
+ }
+
+ for (auto type : {NotificationFlappingStart, NotificationFlappingEnd}) {
+ if (suppressed_types & type) {
+ bool still_applies = NotificationReasonApplies(type);
+
+ if (still_applies) {
+ if (!NotificationReasonSuppressed(type) && !IsLikelyToBeCheckedSoon() && !wasLastParentRecoveryRecent.Get()) {
+ Checkable::OnNotificationsRequested(this, type, GetLastCheckResult(), "", "", nullptr);
+
+ subtract |= type;
+ }
+ } else {
+ subtract |= type;
+ }
+ }
+ }
+ }
+
+ if (subtract) {
+ ObjectLock olock (this);
+
+ int suppressed_types_before (GetSuppressedNotifications());
+ int suppressed_types_after (suppressed_types_before & ~subtract);
+
+ if (suppressed_types_after != suppressed_types_before) {
+ SetSuppressedNotifications(suppressed_types_after);
+ }
+ }
+}
+
+/**
+ * Re-sends all notifications previously suppressed by e.g. downtimes if the notification reason still applies.
+ */
+void Checkable::FireSuppressedNotificationsTimer(const Timer * const&)
+{
+ for (auto& host : ConfigType::GetObjectsByType<Host>()) {
+ host->FireSuppressedNotifications();
+ }
+
+ for (auto& service : ConfigType::GetObjectsByType<Service>()) {
+ service->FireSuppressedNotifications();
+ }
+}
+
+/**
+ * Returns whether sending a notification of type type right now would represent *this' current state correctly.
+ *
+ * @param type The type of notification to send (or not to send).
+ *
+ * @return Whether to send the notification.
+ */
+bool Checkable::NotificationReasonApplies(NotificationType type)
+{
+ switch (type) {
+ case NotificationProblem:
+ {
+ auto cr (GetLastCheckResult());
+ return cr && !IsStateOK(cr->GetState()) && cr->GetState() != GetStateBeforeSuppression();
+ }
+ case NotificationRecovery:
+ {
+ auto cr (GetLastCheckResult());
+ return cr && IsStateOK(cr->GetState()) && cr->GetState() != GetStateBeforeSuppression();
+ }
+ case NotificationFlappingStart:
+ return IsFlapping();
+ case NotificationFlappingEnd:
+ return !IsFlapping();
+ default:
+ VERIFY(!"Checkable#NotificationReasonStillApplies(): given type not implemented");
+ return false;
+ }
+}
+
+/**
+ * Checks if notifications of a given type should be suppressed for this Checkable at the moment.
+ *
+ * @param type The notification type for which to query the suppression status.
+ *
+ * @return true if no notification of this type should be sent.
+ */
+bool Checkable::NotificationReasonSuppressed(NotificationType type)
+{
+ switch (type) {
+ case NotificationProblem:
+ case NotificationRecovery:
+ return !IsReachable(DependencyNotification) || IsInDowntime() || IsAcknowledged();
+ case NotificationFlappingStart:
+ case NotificationFlappingEnd:
+ return IsInDowntime();
+ default:
+ return false;
+ }
+}
+
+/**
+ * E.g. we're going to re-send a stashed problem notification as *this is still not ok.
+ * But if the next check result recovers *this soon, we would send a recovery notification soon after the problem one.
+ * This is not desired, especially for lots of checkables at once.
+ * Because of that if there's likely to be a check result soon,
+ * we delay the re-sending of the stashed notification until the next check.
+ * That check either doesn't change anything and we finally re-send the stashed problem notification
+ * or recovers *this and we drop the stashed notification.
+ *
+ * @return Whether *this is likely to be checked soon
+ */
+bool Checkable::IsLikelyToBeCheckedSoon()
+{
+ if (!GetEnableActiveChecks()) {
+ return false;
+ }
+
+ // One minute unless the check interval is too short so the next check will always run during the next minute.
+ auto threshold (GetCheckInterval() - 10);
+
+ if (threshold > 60) {
+ threshold = 60;
+ } else if (threshold < 0) {
+ threshold = 0;
+ }
+
+ return GetNextCheck() <= Utility::GetTime() + threshold;
+}
diff --git a/lib/icinga/checkable-script.cpp b/lib/icinga/checkable-script.cpp
new file mode 100644
index 0000000..4a0d1d8
--- /dev/null
+++ b/lib/icinga/checkable-script.cpp
@@ -0,0 +1,28 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "base/configobject.hpp"
+#include "base/dictionary.hpp"
+#include "base/function.hpp"
+#include "base/functionwrapper.hpp"
+#include "base/scriptframe.hpp"
+
+using namespace icinga;
+
+static void CheckableProcessCheckResult(const CheckResult::Ptr& cr)
+{
+ ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
+ Checkable::Ptr self = vframe->Self;
+ REQUIRE_NOT_NULL(self);
+ self->ProcessCheckResult(cr);
+}
+
+Object::Ptr Checkable::GetPrototype()
+{
+ static Dictionary::Ptr prototype = new Dictionary({
+ { "process_check_result", new Function("Checkable#process_check_result", CheckableProcessCheckResult, { "cr" }, false) }
+ });
+
+ return prototype;
+}
+
diff --git a/lib/icinga/checkable.cpp b/lib/icinga/checkable.cpp
new file mode 100644
index 0000000..ddf84cd
--- /dev/null
+++ b/lib/icinga/checkable.cpp
@@ -0,0 +1,322 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/checkable-ti.cpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "base/objectlock.hpp"
+#include "base/utility.hpp"
+#include "base/exception.hpp"
+#include "base/timer.hpp"
+#include <boost/thread/once.hpp>
+
+using namespace icinga;
+
+REGISTER_TYPE_WITH_PROTOTYPE(Checkable, Checkable::GetPrototype());
+INITIALIZE_ONCE(&Checkable::StaticInitialize);
+
+const std::map<String, int> Checkable::m_FlappingStateFilterMap ({
+ {"OK", FlappingStateFilterOk},
+ {"Warning", FlappingStateFilterWarning},
+ {"Critical", FlappingStateFilterCritical},
+ {"Unknown", FlappingStateFilterUnknown},
+ {"Up", FlappingStateFilterOk},
+ {"Down", FlappingStateFilterCritical},
+});
+
+boost::signals2::signal<void (const Checkable::Ptr&, const String&, const String&, AcknowledgementType, bool, bool, double, double, const MessageOrigin::Ptr&)> Checkable::OnAcknowledgementSet;
+boost::signals2::signal<void (const Checkable::Ptr&, const String&, double, const MessageOrigin::Ptr&)> Checkable::OnAcknowledgementCleared;
+boost::signals2::signal<void (const Checkable::Ptr&, double)> Checkable::OnFlappingChange;
+
+static Timer::Ptr l_CheckablesFireSuppressedNotifications;
+static Timer::Ptr l_CleanDeadlinedExecutions;
+
+thread_local std::function<void(const Value& commandLine, const ProcessResult&)> Checkable::ExecuteCommandProcessFinishedHandler;
+
+void Checkable::StaticInitialize()
+{
+ /* fixed downtime start */
+ Downtime::OnDowntimeStarted.connect([](const Downtime::Ptr& downtime) { Checkable::NotifyFixedDowntimeStart(downtime); });
+ /* flexible downtime start */
+ Downtime::OnDowntimeTriggered.connect([](const Downtime::Ptr& downtime) { Checkable::NotifyFlexibleDowntimeStart(downtime); });
+ /* fixed/flexible downtime end */
+ Downtime::OnDowntimeRemoved.connect([](const Downtime::Ptr& downtime) { Checkable::NotifyDowntimeEnd(downtime); });
+}
+
+Checkable::Checkable()
+{
+ SetSchedulingOffset(Utility::Random());
+}
+
+void Checkable::OnConfigLoaded()
+{
+ ObjectImpl<Checkable>::OnConfigLoaded();
+
+ SetFlappingIgnoreStatesFilter(FilterArrayToInt(GetFlappingIgnoreStates(), m_FlappingStateFilterMap, ~0));
+}
+
+void Checkable::OnAllConfigLoaded()
+{
+ ObjectImpl<Checkable>::OnAllConfigLoaded();
+
+ Endpoint::Ptr endpoint = GetCommandEndpoint();
+
+ if (endpoint) {
+ Zone::Ptr checkableZone = static_pointer_cast<Zone>(GetZone());
+
+ if (checkableZone) {
+ Zone::Ptr cmdZone = endpoint->GetZone();
+
+ if (cmdZone != checkableZone && cmdZone->GetParent() != checkableZone) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "command_endpoint" },
+ "Command endpoint must be in zone '" + checkableZone->GetName() + "' or in a direct child zone thereof."));
+ }
+ } else {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "command_endpoint" },
+ "Checkable with command endpoint requires a zone. Please check the troubleshooting documentation."));
+ }
+ }
+}
+
+void Checkable::Start(bool runtimeCreated)
+{
+ double now = Utility::GetTime();
+
+ {
+ auto cr (GetLastCheckResult());
+
+ if (GetLastCheckStarted() > (cr ? cr->GetExecutionEnd() : 0.0)) {
+ SetNextCheck(GetLastCheckStarted());
+ }
+ }
+
+ if (GetNextCheck() < now + 60) {
+ double delta = std::min(GetCheckInterval(), 60.0);
+ delta *= (double)std::rand() / RAND_MAX;
+ SetNextCheck(now + delta);
+ }
+
+ ObjectImpl<Checkable>::Start(runtimeCreated);
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, []() {
+ l_CheckablesFireSuppressedNotifications = Timer::Create();
+ l_CheckablesFireSuppressedNotifications->SetInterval(5);
+ l_CheckablesFireSuppressedNotifications->OnTimerExpired.connect(&Checkable::FireSuppressedNotificationsTimer);
+ l_CheckablesFireSuppressedNotifications->Start();
+
+ l_CleanDeadlinedExecutions = Timer::Create();
+ l_CleanDeadlinedExecutions->SetInterval(300);
+ l_CleanDeadlinedExecutions->OnTimerExpired.connect(&Checkable::CleanDeadlinedExecutions);
+ l_CleanDeadlinedExecutions->Start();
+ });
+}
+
+void Checkable::AddGroup(const String& name)
+{
+ std::unique_lock<std::mutex> lock(m_CheckableMutex);
+
+ Array::Ptr groups;
+ auto *host = dynamic_cast<Host *>(this);
+
+ if (host)
+ groups = host->GetGroups();
+ else
+ groups = static_cast<Service *>(this)->GetGroups();
+
+ if (groups && groups->Contains(name))
+ return;
+
+ if (!groups)
+ groups = new Array();
+
+ groups->Add(name);
+}
+
+AcknowledgementType Checkable::GetAcknowledgement()
+{
+ auto avalue = static_cast<AcknowledgementType>(GetAcknowledgementRaw());
+
+ if (avalue != AcknowledgementNone) {
+ double expiry = GetAcknowledgementExpiry();
+
+ if (expiry != 0 && expiry < Utility::GetTime()) {
+ avalue = AcknowledgementNone;
+ ClearAcknowledgement("");
+ }
+ }
+
+ return avalue;
+}
+
+bool Checkable::IsAcknowledged() const
+{
+ return const_cast<Checkable *>(this)->GetAcknowledgement() != AcknowledgementNone;
+}
+
+void Checkable::AcknowledgeProblem(const String& author, const String& comment, AcknowledgementType type, bool notify, bool persistent, double changeTime, double expiry, const MessageOrigin::Ptr& origin)
+{
+ SetAcknowledgementRaw(type);
+ SetAcknowledgementExpiry(expiry);
+
+ if (notify && !IsPaused())
+ OnNotificationsRequested(this, NotificationAcknowledgement, GetLastCheckResult(), author, comment, nullptr);
+
+ Log(LogInformation, "Checkable")
+ << "Acknowledgement set for checkable '" << GetName() << "'.";
+
+ OnAcknowledgementSet(this, author, comment, type, notify, persistent, changeTime, expiry, origin);
+
+ SetAcknowledgementLastChange(changeTime);
+}
+
+void Checkable::ClearAcknowledgement(const String& removedBy, double changeTime, const MessageOrigin::Ptr& origin)
+{
+ ObjectLock oLock (this);
+
+ bool wasAcked = GetAcknowledgementRaw() != AcknowledgementNone;
+
+ SetAcknowledgementRaw(AcknowledgementNone);
+ SetAcknowledgementExpiry(0);
+
+ Log(LogInformation, "Checkable")
+ << "Acknowledgement cleared for checkable '" << GetName() << "'.";
+
+ if (wasAcked) {
+ OnAcknowledgementCleared(this, removedBy, changeTime, origin);
+
+ SetAcknowledgementLastChange(changeTime);
+ }
+}
+
+Endpoint::Ptr Checkable::GetCommandEndpoint() const
+{
+ return Endpoint::GetByName(GetCommandEndpointRaw());
+}
+
+int Checkable::GetSeverity() const
+{
+ /* overridden in Host/Service class. */
+ return 0;
+}
+
+bool Checkable::GetProblem() const
+{
+ auto cr (GetLastCheckResult());
+
+ return cr && !IsStateOK(cr->GetState());
+}
+
+bool Checkable::GetHandled() const
+{
+ return GetProblem() && (IsInDowntime() || IsAcknowledged());
+}
+
+Timestamp Checkable::GetNextUpdate() const
+{
+ auto cr (GetLastCheckResult());
+ double interval, latency;
+
+ // TODO: Document this behavior.
+ if (cr) {
+ interval = GetEnableActiveChecks() && GetProblem() && GetStateType() == StateTypeSoft ? GetRetryInterval() : GetCheckInterval();
+ latency = cr->GetExecutionEnd() - cr->GetScheduleStart();
+ } else {
+ interval = GetCheckInterval();
+ latency = 0.0;
+ }
+
+ return (GetEnableActiveChecks() ? GetNextCheck() : (cr ? cr->GetExecutionEnd() : Application::GetStartTime()) + interval) + interval + 2 * latency;
+}
+
+void Checkable::NotifyFixedDowntimeStart(const Downtime::Ptr& downtime)
+{
+ if (!downtime->GetFixed())
+ return;
+
+ NotifyDowntimeInternal(downtime);
+}
+
+void Checkable::NotifyFlexibleDowntimeStart(const Downtime::Ptr& downtime)
+{
+ if (downtime->GetFixed())
+ return;
+
+ NotifyDowntimeInternal(downtime);
+}
+
+void Checkable::NotifyDowntimeInternal(const Downtime::Ptr& downtime)
+{
+ Checkable::Ptr checkable = downtime->GetCheckable();
+
+ if (!checkable->IsPaused())
+ OnNotificationsRequested(checkable, NotificationDowntimeStart, checkable->GetLastCheckResult(), downtime->GetAuthor(), downtime->GetComment(), nullptr);
+}
+
+void Checkable::NotifyDowntimeEnd(const Downtime::Ptr& downtime)
+{
+ /* don't send notifications for downtimes which never triggered */
+ if (!downtime->IsTriggered())
+ return;
+
+ Checkable::Ptr checkable = downtime->GetCheckable();
+
+ if (!checkable->IsPaused())
+ OnNotificationsRequested(checkable, NotificationDowntimeEnd, checkable->GetLastCheckResult(), downtime->GetAuthor(), downtime->GetComment(), nullptr);
+}
+
+void Checkable::ValidateCheckInterval(const Lazy<double>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Checkable>::ValidateCheckInterval(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "check_interval" }, "Interval must be greater than 0."));
+}
+
+void Checkable::ValidateRetryInterval(const Lazy<double>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Checkable>::ValidateRetryInterval(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "retry_interval" }, "Interval must be greater than 0."));
+}
+
+void Checkable::ValidateMaxCheckAttempts(const Lazy<int>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Checkable>::ValidateMaxCheckAttempts(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "max_check_attempts" }, "Value must be greater than 0."));
+}
+
+void Checkable::CleanDeadlinedExecutions(const Timer * const&)
+{
+ double now = Utility::GetTime();
+ Dictionary::Ptr executions;
+ Dictionary::Ptr execution;
+
+ for (auto& host : ConfigType::GetObjectsByType<Host>()) {
+ executions = host->GetExecutions();
+ if (executions) {
+ for (const String& key : executions->GetKeys()) {
+ execution = executions->Get(key);
+ if (execution->Contains("deadline") && now > execution->Get("deadline")) {
+ executions->Remove(key);
+ }
+ }
+ }
+ }
+
+ for (auto& service : ConfigType::GetObjectsByType<Service>()) {
+ executions = service->GetExecutions();
+ if (executions) {
+ for (const String& key : executions->GetKeys()) {
+ execution = executions->Get(key);
+ if (execution->Contains("deadline") && now > execution->Get("deadline")) {
+ executions->Remove(key);
+ }
+ }
+ }
+ }
+}
diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp
new file mode 100644
index 0000000..3d48b14
--- /dev/null
+++ b/lib/icinga/checkable.hpp
@@ -0,0 +1,264 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CHECKABLE_H
+#define CHECKABLE_H
+
+#include "base/atomic.hpp"
+#include "base/timer.hpp"
+#include "base/process.hpp"
+#include "icinga/i2-icinga.hpp"
+#include "icinga/checkable-ti.hpp"
+#include "icinga/timeperiod.hpp"
+#include "icinga/notification.hpp"
+#include "icinga/comment.hpp"
+#include "icinga/downtime.hpp"
+#include "remote/endpoint.hpp"
+#include "remote/messageorigin.hpp"
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <limits>
+
+namespace icinga
+{
+
+/**
+ * @ingroup icinga
+ */
+enum DependencyType
+{
+ DependencyState,
+ DependencyCheckExecution,
+ DependencyNotification
+};
+
+/**
+ * Checkable Types
+ *
+ * @ingroup icinga
+ */
+enum CheckableType
+{
+ CheckableHost,
+ CheckableService
+};
+
+/**
+ * @ingroup icinga
+ */
+enum FlappingStateFilter
+{
+ FlappingStateFilterOk = 1,
+ FlappingStateFilterWarning = 2,
+ FlappingStateFilterCritical = 4,
+ FlappingStateFilterUnknown = 8,
+};
+
+class CheckCommand;
+class EventCommand;
+class Dependency;
+
+/**
+ * An Icinga service.
+ *
+ * @ingroup icinga
+ */
+class Checkable : public ObjectImpl<Checkable>
+{
+public:
+ DECLARE_OBJECT(Checkable);
+ DECLARE_OBJECTNAME(Checkable);
+
+ static void StaticInitialize();
+ static thread_local std::function<void(const Value& commandLine, const ProcessResult&)> ExecuteCommandProcessFinishedHandler;
+
+ Checkable();
+
+ std::set<Checkable::Ptr> GetParents() const;
+ std::set<Checkable::Ptr> GetChildren() const;
+ std::set<Checkable::Ptr> GetAllChildren() const;
+
+ void AddGroup(const String& name);
+
+ bool IsReachable(DependencyType dt = DependencyState, intrusive_ptr<Dependency> *failedDependency = nullptr, int rstack = 0) const;
+
+ AcknowledgementType GetAcknowledgement();
+
+ void AcknowledgeProblem(const String& author, const String& comment, AcknowledgementType type, bool notify = true, bool persistent = false, double changeTime = Utility::GetTime(), double expiry = 0, const MessageOrigin::Ptr& origin = nullptr);
+ void ClearAcknowledgement(const String& removedBy, double changeTime = Utility::GetTime(), const MessageOrigin::Ptr& origin = nullptr);
+
+ int GetSeverity() const override;
+ bool GetProblem() const override;
+ bool GetHandled() const override;
+ Timestamp GetNextUpdate() const override;
+
+ /* Checks */
+ intrusive_ptr<CheckCommand> GetCheckCommand() const;
+ TimePeriod::Ptr GetCheckPeriod() const;
+
+ long GetSchedulingOffset();
+ void SetSchedulingOffset(long offset);
+
+ void UpdateNextCheck(const MessageOrigin::Ptr& origin = nullptr);
+
+ bool HasBeenChecked() const;
+ virtual bool IsStateOK(ServiceState state) const = 0;
+
+ double GetLastCheck() const final;
+
+ virtual void SaveLastState(ServiceState state, double timestamp) = 0;
+
+ static void UpdateStatistics(const CheckResult::Ptr& cr, CheckableType type);
+
+ void ExecuteRemoteCheck(const Dictionary::Ptr& resolvedMacros = nullptr);
+ void ExecuteCheck();
+ enum class ProcessingResult
+ {
+ Ok,
+ NoCheckResult,
+ CheckableInactive,
+ NewerCheckResultPresent,
+ };
+ ProcessingResult ProcessCheckResult(const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin = nullptr);
+
+ Endpoint::Ptr GetCommandEndpoint() const;
+
+ static boost::signals2::signal<void (const Checkable::Ptr&, const CheckResult::Ptr&, const MessageOrigin::Ptr&)> OnNewCheckResult;
+ static boost::signals2::signal<void (const Checkable::Ptr&, const CheckResult::Ptr&, StateType, const MessageOrigin::Ptr&)> OnStateChange;
+ static boost::signals2::signal<void (const Checkable::Ptr&, const CheckResult::Ptr&, std::set<Checkable::Ptr>, const MessageOrigin::Ptr&)> OnReachabilityChanged;
+ static boost::signals2::signal<void (const Checkable::Ptr&, NotificationType, const CheckResult::Ptr&,
+ const String&, const String&, const MessageOrigin::Ptr&)> OnNotificationsRequested;
+ static boost::signals2::signal<void (const Notification::Ptr&, const Checkable::Ptr&, const User::Ptr&,
+ const NotificationType&, const CheckResult::Ptr&, const String&, const String&, const String&,
+ const MessageOrigin::Ptr&)> OnNotificationSentToUser;
+ static boost::signals2::signal<void (const Notification::Ptr&, const Checkable::Ptr&, const std::set<User::Ptr>&,
+ const NotificationType&, const CheckResult::Ptr&, const String&,
+ const String&, const MessageOrigin::Ptr&)> OnNotificationSentToAllUsers;
+ static boost::signals2::signal<void (const Checkable::Ptr&, const String&, const String&, AcknowledgementType,
+ bool, bool, double, double, const MessageOrigin::Ptr&)> OnAcknowledgementSet;
+ static boost::signals2::signal<void (const Checkable::Ptr&, const String&, double, const MessageOrigin::Ptr&)> OnAcknowledgementCleared;
+ static boost::signals2::signal<void (const Checkable::Ptr&, double)> OnFlappingChange;
+ static boost::signals2::signal<void (const Checkable::Ptr&)> OnNextCheckUpdated;
+ static boost::signals2::signal<void (const Checkable::Ptr&)> OnEventCommandExecuted;
+
+ static Atomic<uint_fast64_t> CurrentConcurrentChecks;
+
+ /* Downtimes */
+ int GetDowntimeDepth() const final;
+
+ void RemoveAllDowntimes();
+ void TriggerDowntimes(double triggerTime);
+ bool IsInDowntime() const;
+ bool IsAcknowledged() const;
+
+ std::set<Downtime::Ptr> GetDowntimes() const;
+ void RegisterDowntime(const Downtime::Ptr& downtime);
+ void UnregisterDowntime(const Downtime::Ptr& downtime);
+
+ /* Comments */
+ void RemoveAllComments();
+ void RemoveAckComments(const String& removedBy = String(), double createdBefore = std::numeric_limits<double>::max());
+
+ std::set<Comment::Ptr> GetComments() const;
+ Comment::Ptr GetLastComment() const;
+ void RegisterComment(const Comment::Ptr& comment);
+ void UnregisterComment(const Comment::Ptr& comment);
+
+ /* Notifications */
+ void SendNotifications(NotificationType type, const CheckResult::Ptr& cr, const String& author = "", const String& text = "");
+
+ std::set<Notification::Ptr> GetNotifications() const;
+ void RegisterNotification(const Notification::Ptr& notification);
+ void UnregisterNotification(const Notification::Ptr& notification);
+
+ void ResetNotificationNumbers();
+
+ /* Event Handler */
+ void ExecuteEventHandler(const Dictionary::Ptr& resolvedMacros = nullptr,
+ bool useResolvedMacros = false);
+
+ intrusive_ptr<EventCommand> GetEventCommand() const;
+
+ /* Flapping Detection */
+ bool IsFlapping() const;
+
+ /* Dependencies */
+ void AddDependency(const intrusive_ptr<Dependency>& dep);
+ void RemoveDependency(const intrusive_ptr<Dependency>& dep);
+ std::vector<intrusive_ptr<Dependency> > GetDependencies() const;
+
+ void AddReverseDependency(const intrusive_ptr<Dependency>& dep);
+ void RemoveReverseDependency(const intrusive_ptr<Dependency>& dep);
+ std::vector<intrusive_ptr<Dependency> > GetReverseDependencies() const;
+
+ void ValidateCheckInterval(const Lazy<double>& lvalue, const ValidationUtils& value) final;
+ void ValidateRetryInterval(const Lazy<double>& lvalue, const ValidationUtils& value) final;
+ void ValidateMaxCheckAttempts(const Lazy<int>& lvalue, const ValidationUtils& value) final;
+
+ bool NotificationReasonApplies(NotificationType type);
+ bool NotificationReasonSuppressed(NotificationType type);
+ bool IsLikelyToBeCheckedSoon();
+
+ void FireSuppressedNotifications();
+
+ static void IncreasePendingChecks();
+ static void DecreasePendingChecks();
+ static int GetPendingChecks();
+ static void AquirePendingCheckSlot(int maxPendingChecks);
+
+ static Object::Ptr GetPrototype();
+
+protected:
+ void Start(bool runtimeCreated) override;
+ void OnConfigLoaded() override;
+ void OnAllConfigLoaded() override;
+
+private:
+ mutable std::mutex m_CheckableMutex;
+ bool m_CheckRunning{false};
+ long m_SchedulingOffset;
+
+ static std::mutex m_StatsMutex;
+ static int m_PendingChecks;
+ static std::condition_variable m_PendingChecksCV;
+
+ /* Downtimes */
+ std::set<Downtime::Ptr> m_Downtimes;
+ mutable std::mutex m_DowntimeMutex;
+
+ static void NotifyFixedDowntimeStart(const Downtime::Ptr& downtime);
+ static void NotifyFlexibleDowntimeStart(const Downtime::Ptr& downtime);
+ static void NotifyDowntimeInternal(const Downtime::Ptr& downtime);
+
+ static void NotifyDowntimeEnd(const Downtime::Ptr& downtime);
+
+ static void FireSuppressedNotificationsTimer(const Timer * const&);
+ static void CleanDeadlinedExecutions(const Timer * const&);
+
+ /* Comments */
+ std::set<Comment::Ptr> m_Comments;
+ mutable std::mutex m_CommentMutex;
+
+ /* Notifications */
+ std::set<Notification::Ptr> m_Notifications;
+ mutable std::mutex m_NotificationMutex;
+
+ /* Dependencies */
+ mutable std::mutex m_DependencyMutex;
+ std::set<intrusive_ptr<Dependency> > m_Dependencies;
+ std::set<intrusive_ptr<Dependency> > m_ReverseDependencies;
+
+ void GetAllChildrenInternal(std::set<Checkable::Ptr>& children, int level = 0) const;
+
+ /* Flapping */
+ static const std::map<String, int> m_FlappingStateFilterMap;
+
+ void UpdateFlappingStatus(ServiceState newState);
+ static int ServiceStateToFlappingFilter(ServiceState state);
+};
+
+}
+
+#endif /* CHECKABLE_H */
+
+#include "icinga/dependency.hpp"
diff --git a/lib/icinga/checkable.ti b/lib/icinga/checkable.ti
new file mode 100644
index 0000000..6f7a5da
--- /dev/null
+++ b/lib/icinga/checkable.ti
@@ -0,0 +1,192 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/icingaapplication.hpp"
+#include "icinga/customvarobject.hpp"
+#include "base/array.hpp"
+#impl_include "icinga/checkcommand.hpp"
+#impl_include "icinga/eventcommand.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+/**
+ * The acknowledgement type of a service.
+ *
+ * @ingroup icinga
+ */
+enum AcknowledgementType
+{
+ AcknowledgementNone = 0,
+ AcknowledgementNormal = 1,
+ AcknowledgementSticky = 2
+};
+}}}
+
+abstract class Checkable : CustomVarObject
+{
+ [config, required, navigation] name(CheckCommand) check_command (CheckCommandRaw) {
+ navigate {{{
+ return CheckCommand::GetByName(GetCheckCommandRaw());
+ }}}
+ };
+ [config] int max_check_attempts {
+ default {{{ return 3; }}}
+ };
+ [config, navigation] name(TimePeriod) check_period (CheckPeriodRaw) {
+ navigate {{{
+ return TimePeriod::GetByName(GetCheckPeriodRaw());
+ }}}
+ };
+ [config] Value check_timeout;
+ [config] double check_interval {
+ default {{{ return 5 * 60; }}}
+ };
+ [config] double retry_interval {
+ default {{{ return 60; }}}
+ };
+ [config, navigation] name(EventCommand) event_command (EventCommandRaw) {
+ navigate {{{
+ return EventCommand::GetByName(GetEventCommandRaw());
+ }}}
+ };
+ [config] bool volatile;
+
+ [config] bool enable_active_checks {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_passive_checks {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_event_handler {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_notifications {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_flapping {
+ default {{{ return false; }}}
+ };
+ [config] bool enable_perfdata {
+ default {{{ return true; }}}
+ };
+
+ [config] array(String) flapping_ignore_states;
+ [no_user_view, no_user_modify] int flapping_ignore_states_filter_real (FlappingIgnoreStatesFilter);
+
+ [config, deprecated] double flapping_threshold;
+
+ [config] double flapping_threshold_low {
+ default {{{ return 25; }}}
+ };
+
+ [config] double flapping_threshold_high{
+ default {{{ return 30; }}}
+ };
+
+ [config] String notes;
+ [config] String notes_url;
+ [config] String action_url;
+ [config] String icon_image;
+ [config] String icon_image_alt;
+
+ [state] Timestamp next_check;
+ [state, no_user_view, no_user_modify] Timestamp last_check_started;
+
+ [state] int check_attempt {
+ default {{{ return 1; }}}
+ };
+ [state, enum, no_user_view, no_user_modify] ServiceState state_raw {
+ default {{{ return ServiceUnknown; }}}
+ };
+ [state, enum] StateType state_type {
+ default {{{ return StateTypeSoft; }}}
+ };
+ [state, enum, no_user_view, no_user_modify] ServiceState last_state_raw {
+ default {{{ return ServiceUnknown; }}}
+ };
+ [state, enum, no_user_view, no_user_modify] ServiceState last_hard_state_raw {
+ default {{{ return ServiceUnknown; }}}
+ };
+ [state, no_user_view, no_user_modify] "unsigned short" last_hard_states_raw {
+ default {{{ return /* current */ 99 * 100 + /* previous */ 99; }}}
+ };
+ [state, no_user_view, no_user_modify] "unsigned short" last_soft_states_raw {
+ default {{{ return /* current */ 99 * 100 + /* previous */ 99; }}}
+ };
+ [state, enum] StateType last_state_type {
+ default {{{ return StateTypeSoft; }}}
+ };
+ [state] bool last_reachable {
+ default {{{ return true; }}}
+ };
+ [state] CheckResult::Ptr last_check_result;
+ [state] Timestamp last_state_change {
+ default {{{ return Application::GetStartTime(); }}}
+ };
+ [state] Timestamp last_hard_state_change {
+ default {{{ return Application::GetStartTime(); }}}
+ };
+ [state] Timestamp last_state_unreachable;
+
+ [state] Timestamp previous_state_change {
+ default {{{ return Application::GetStartTime(); }}}
+ };
+ [no_storage] int severity {
+ get;
+ };
+ [no_storage] bool problem {
+ get;
+ };
+ [no_storage] bool handled {
+ get;
+ };
+ [no_storage] Timestamp next_update {
+ get;
+ };
+
+ [state] bool force_next_check;
+ [state] int acknowledgement (AcknowledgementRaw) {
+ default {{{ return AcknowledgementNone; }}}
+ };
+ [state] Timestamp acknowledgement_expiry;
+ [state] Timestamp acknowledgement_last_change;
+ [state] bool force_next_notification;
+ [no_storage] Timestamp last_check {
+ get;
+ };
+ [no_storage] int downtime_depth {
+ get;
+ };
+
+ [state] double flapping_current {
+ default {{{ return 0; }}}
+ };
+ [state] Timestamp flapping_last_change;
+
+ [state, enum, no_user_view, no_user_modify] ServiceState flapping_last_state {
+ default {{{ return ServiceUnknown; }}}
+ };
+ [state, no_user_view, no_user_modify] int flapping_buffer;
+ [state, no_user_view, no_user_modify] int flapping_index;
+ [state, protected] bool flapping;
+ [state, no_user_view, no_user_modify] int suppressed_notifications {
+ default {{{ return 0; }}}
+ };
+ [state, enum, no_user_view, no_user_modify] ServiceState state_before_suppression {
+ default {{{ return ServiceOK; }}}
+ };
+
+ [config, navigation] name(Endpoint) command_endpoint (CommandEndpointRaw) {
+ navigate {{{
+ return Endpoint::GetByName(GetCommandEndpointRaw());
+ }}}
+ };
+
+ [state, no_user_modify] Dictionary::Ptr executions;
+ [state, no_user_view, no_user_modify] Dictionary::Ptr pending_executions;
+};
+
+}
diff --git a/lib/icinga/checkcommand.cpp b/lib/icinga/checkcommand.cpp
new file mode 100644
index 0000000..fb8032a
--- /dev/null
+++ b/lib/icinga/checkcommand.cpp
@@ -0,0 +1,22 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkcommand.hpp"
+#include "icinga/checkcommand-ti.cpp"
+#include "base/configtype.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(CheckCommand);
+
+thread_local CheckCommand::Ptr CheckCommand::ExecuteOverride;
+
+void CheckCommand::Execute(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros)
+{
+ GetExecute()->Invoke({
+ checkable,
+ cr,
+ resolvedMacros,
+ useResolvedMacros
+ });
+}
diff --git a/lib/icinga/checkcommand.hpp b/lib/icinga/checkcommand.hpp
new file mode 100644
index 0000000..c654cf9
--- /dev/null
+++ b/lib/icinga/checkcommand.hpp
@@ -0,0 +1,32 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CHECKCOMMAND_H
+#define CHECKCOMMAND_H
+
+#include "icinga/checkcommand-ti.hpp"
+#include "icinga/checkable.hpp"
+
+namespace icinga
+{
+
+/**
+ * A command.
+ *
+ * @ingroup icinga
+ */
+class CheckCommand final : public ObjectImpl<CheckCommand>
+{
+public:
+ DECLARE_OBJECT(CheckCommand);
+ DECLARE_OBJECTNAME(CheckCommand);
+
+ static thread_local CheckCommand::Ptr ExecuteOverride;
+
+ void Execute(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr,
+ const Dictionary::Ptr& resolvedMacros = nullptr,
+ bool useResolvedMacros = false);
+};
+
+}
+
+#endif /* CHECKCOMMAND_H */
diff --git a/lib/icinga/checkcommand.ti b/lib/icinga/checkcommand.ti
new file mode 100644
index 0000000..c211f0f
--- /dev/null
+++ b/lib/icinga/checkcommand.ti
@@ -0,0 +1,14 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/command.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class CheckCommand : Command
+{
+};
+
+}
diff --git a/lib/icinga/checkresult.cpp b/lib/icinga/checkresult.cpp
new file mode 100644
index 0000000..07f7219
--- /dev/null
+++ b/lib/icinga/checkresult.cpp
@@ -0,0 +1,34 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkresult.hpp"
+#include "icinga/checkresult-ti.cpp"
+#include "base/scriptglobal.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(CheckResult);
+
+INITIALIZE_ONCE([]() {
+ ScriptGlobal::Set("Icinga.ServiceOK", ServiceOK);
+ ScriptGlobal::Set("Icinga.ServiceWarning", ServiceWarning);
+ ScriptGlobal::Set("Icinga.ServiceCritical", ServiceCritical);
+ ScriptGlobal::Set("Icinga.ServiceUnknown", ServiceUnknown);
+
+ ScriptGlobal::Set("Icinga.HostUp", HostUp);
+ ScriptGlobal::Set("Icinga.HostDown", HostDown);
+})
+
+double CheckResult::CalculateExecutionTime() const
+{
+ return GetExecutionEnd() - GetExecutionStart();
+}
+
+double CheckResult::CalculateLatency() const
+{
+ double latency = (GetScheduleEnd() - GetScheduleStart()) - CalculateExecutionTime();
+
+ if (latency < 0)
+ latency = 0;
+
+ return latency;
+}
diff --git a/lib/icinga/checkresult.hpp b/lib/icinga/checkresult.hpp
new file mode 100644
index 0000000..ac54d6b
--- /dev/null
+++ b/lib/icinga/checkresult.hpp
@@ -0,0 +1,28 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CHECKRESULT_H
+#define CHECKRESULT_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/checkresult-ti.hpp"
+
+namespace icinga
+{
+
+/**
+ * A check result.
+ *
+ * @ingroup icinga
+ */
+class CheckResult final : public ObjectImpl<CheckResult>
+{
+public:
+ DECLARE_OBJECT(CheckResult);
+
+ double CalculateExecutionTime() const;
+ double CalculateLatency() const;
+};
+
+}
+
+#endif /* CHECKRESULT_H */
diff --git a/lib/icinga/checkresult.ti b/lib/icinga/checkresult.ti
new file mode 100644
index 0000000..09312dc
--- /dev/null
+++ b/lib/icinga/checkresult.ti
@@ -0,0 +1,72 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+/**
+ * The state of a host.
+ *
+ * @ingroup icinga
+ */
+enum HostState
+{
+ HostUp = 0,
+ HostDown = 1
+};
+
+/**
+ * The state of a service.
+ *
+ * @ingroup icinga
+ */
+enum ServiceState
+{
+ ServiceOK = 0,
+ ServiceWarning = 1,
+ ServiceCritical = 2,
+ ServiceUnknown = 3
+};
+
+/**
+ * The state type of a host or service.
+ *
+ * @ingroup icinga
+ */
+enum StateType
+{
+ StateTypeSoft = 0,
+ StateTypeHard = 1
+};
+}}}
+
+class CheckResult
+{
+ [state] Timestamp schedule_start;
+ [state] Timestamp schedule_end;
+ [state] Timestamp execution_start;
+ [state] Timestamp execution_end;
+
+ [state] Value command;
+ [state] int exit_status;
+
+ [state, enum] ServiceState "state";
+ [state, enum] ServiceState previous_hard_state;
+ [state] String output;
+ [state] Array::Ptr performance_data;
+
+ [state] bool active {
+ default {{{ return true; }}}
+ };
+
+ [state] String check_source;
+ [state] String scheduling_source;
+ [state] double ttl;
+
+ [state] Dictionary::Ptr vars_before;
+ [state] Dictionary::Ptr vars_after;
+};
+
+}
diff --git a/lib/icinga/cib.cpp b/lib/icinga/cib.cpp
new file mode 100644
index 0000000..ce71a59
--- /dev/null
+++ b/lib/icinga/cib.cpp
@@ -0,0 +1,346 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/cib.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "icinga/clusterevents.hpp"
+#include "base/application.hpp"
+#include "base/objectlock.hpp"
+#include "base/utility.hpp"
+#include "base/perfdatavalue.hpp"
+#include "base/configtype.hpp"
+#include "base/statsfunction.hpp"
+
+using namespace icinga;
+
+RingBuffer CIB::m_ActiveHostChecksStatistics(15 * 60);
+RingBuffer CIB::m_ActiveServiceChecksStatistics(15 * 60);
+RingBuffer CIB::m_PassiveHostChecksStatistics(15 * 60);
+RingBuffer CIB::m_PassiveServiceChecksStatistics(15 * 60);
+
+void CIB::UpdateActiveHostChecksStatistics(long tv, int num)
+{
+ m_ActiveHostChecksStatistics.InsertValue(tv, num);
+}
+
+void CIB::UpdateActiveServiceChecksStatistics(long tv, int num)
+{
+ m_ActiveServiceChecksStatistics.InsertValue(tv, num);
+}
+
+int CIB::GetActiveHostChecksStatistics(long timespan)
+{
+ return m_ActiveHostChecksStatistics.UpdateAndGetValues(Utility::GetTime(), timespan);
+}
+
+int CIB::GetActiveServiceChecksStatistics(long timespan)
+{
+ return m_ActiveServiceChecksStatistics.UpdateAndGetValues(Utility::GetTime(), timespan);
+}
+
+void CIB::UpdatePassiveHostChecksStatistics(long tv, int num)
+{
+ m_PassiveServiceChecksStatistics.InsertValue(tv, num);
+}
+
+void CIB::UpdatePassiveServiceChecksStatistics(long tv, int num)
+{
+ m_PassiveServiceChecksStatistics.InsertValue(tv, num);
+}
+
+int CIB::GetPassiveHostChecksStatistics(long timespan)
+{
+ return m_PassiveHostChecksStatistics.UpdateAndGetValues(Utility::GetTime(), timespan);
+}
+
+int CIB::GetPassiveServiceChecksStatistics(long timespan)
+{
+ return m_PassiveServiceChecksStatistics.UpdateAndGetValues(Utility::GetTime(), timespan);
+}
+
+CheckableCheckStatistics CIB::CalculateHostCheckStats()
+{
+ double min_latency = -1, max_latency = 0, sum_latency = 0;
+ int count_latency = 0;
+ double min_execution_time = -1, max_execution_time = 0, sum_execution_time = 0;
+ int count_execution_time = 0;
+ bool checkresult = false;
+
+ for (const Host::Ptr& host : ConfigType::GetObjectsByType<Host>()) {
+ ObjectLock olock(host);
+
+ CheckResult::Ptr cr = host->GetLastCheckResult();
+
+ if (!cr)
+ continue;
+
+ /* set to true, we have a checkresult */
+ checkresult = true;
+
+ /* latency */
+ double latency = cr->CalculateLatency();
+
+ if (min_latency == -1 || latency < min_latency)
+ min_latency = latency;
+
+ if (latency > max_latency)
+ max_latency = latency;
+
+ sum_latency += latency;
+ count_latency++;
+
+ /* execution_time */
+ double execution_time = cr->CalculateExecutionTime();
+
+ if (min_execution_time == -1 || execution_time < min_execution_time)
+ min_execution_time = execution_time;
+
+ if (execution_time > max_execution_time)
+ max_execution_time = execution_time;
+
+ sum_execution_time += execution_time;
+ count_execution_time++;
+ }
+
+ if (!checkresult) {
+ min_latency = 0;
+ min_execution_time = 0;
+ }
+
+ CheckableCheckStatistics ccs;
+
+ ccs.min_latency = min_latency;
+ ccs.max_latency = max_latency;
+ ccs.avg_latency = sum_latency / count_latency;
+ ccs.min_execution_time = min_execution_time;
+ ccs.max_execution_time = max_execution_time;
+ ccs.avg_execution_time = sum_execution_time / count_execution_time;
+
+ return ccs;
+}
+
+CheckableCheckStatistics CIB::CalculateServiceCheckStats()
+{
+ double min_latency = -1, max_latency = 0, sum_latency = 0;
+ int count_latency = 0;
+ double min_execution_time = -1, max_execution_time = 0, sum_execution_time = 0;
+ int count_execution_time = 0;
+ bool checkresult = false;
+
+ for (const Service::Ptr& service : ConfigType::GetObjectsByType<Service>()) {
+ ObjectLock olock(service);
+
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (!cr)
+ continue;
+
+ /* set to true, we have a checkresult */
+ checkresult = true;
+
+ /* latency */
+ double latency = cr->CalculateLatency();
+
+ if (min_latency == -1 || latency < min_latency)
+ min_latency = latency;
+
+ if (latency > max_latency)
+ max_latency = latency;
+
+ sum_latency += latency;
+ count_latency++;
+
+ /* execution_time */
+ double execution_time = cr->CalculateExecutionTime();
+
+ if (min_execution_time == -1 || execution_time < min_execution_time)
+ min_execution_time = execution_time;
+
+ if (execution_time > max_execution_time)
+ max_execution_time = execution_time;
+
+ sum_execution_time += execution_time;
+ count_execution_time++;
+ }
+
+ if (!checkresult) {
+ min_latency = 0;
+ min_execution_time = 0;
+ }
+
+ CheckableCheckStatistics ccs;
+
+ ccs.min_latency = min_latency;
+ ccs.max_latency = max_latency;
+ ccs.avg_latency = sum_latency / count_latency;
+ ccs.min_execution_time = min_execution_time;
+ ccs.max_execution_time = max_execution_time;
+ ccs.avg_execution_time = sum_execution_time / count_execution_time;
+
+ return ccs;
+}
+
+ServiceStatistics CIB::CalculateServiceStats()
+{
+ ServiceStatistics ss = {};
+
+ for (const Service::Ptr& service : ConfigType::GetObjectsByType<Service>()) {
+ ObjectLock olock(service);
+
+ if (service->GetState() == ServiceOK)
+ ss.services_ok++;
+ if (service->GetState() == ServiceWarning)
+ ss.services_warning++;
+ if (service->GetState() == ServiceCritical)
+ ss.services_critical++;
+ if (service->GetState() == ServiceUnknown)
+ ss.services_unknown++;
+
+ CheckResult::Ptr cr = service->GetLastCheckResult();
+
+ if (!cr)
+ ss.services_pending++;
+
+ if (!service->IsReachable())
+ ss.services_unreachable++;
+
+ if (service->IsFlapping())
+ ss.services_flapping++;
+ if (service->IsInDowntime())
+ ss.services_in_downtime++;
+ if (service->IsAcknowledged())
+ ss.services_acknowledged++;
+
+ if (service->GetHandled())
+ ss.services_handled++;
+ if (service->GetProblem())
+ ss.services_problem++;
+ }
+
+ return ss;
+}
+
+HostStatistics CIB::CalculateHostStats()
+{
+ HostStatistics hs = {};
+
+ for (const Host::Ptr& host : ConfigType::GetObjectsByType<Host>()) {
+ ObjectLock olock(host);
+
+ if (host->IsReachable()) {
+ if (host->GetState() == HostUp)
+ hs.hosts_up++;
+ if (host->GetState() == HostDown)
+ hs.hosts_down++;
+ } else
+ hs.hosts_unreachable++;
+
+ if (!host->GetLastCheckResult())
+ hs.hosts_pending++;
+
+ if (host->IsFlapping())
+ hs.hosts_flapping++;
+ if (host->IsInDowntime())
+ hs.hosts_in_downtime++;
+ if (host->IsAcknowledged())
+ hs.hosts_acknowledged++;
+
+ if (host->GetHandled())
+ hs.hosts_handled++;
+ if (host->GetProblem())
+ hs.hosts_problem++;
+ }
+
+ return hs;
+}
+
+/*
+ * 'perfdata' must be a flat dictionary with double values
+ * 'status' dictionary can contain multiple levels of dictionaries
+ */
+std::pair<Dictionary::Ptr, Array::Ptr> CIB::GetFeatureStats()
+{
+ Dictionary::Ptr status = new Dictionary();
+ Array::Ptr perfdata = new Array();
+
+ Namespace::Ptr statsFunctions = ScriptGlobal::Get("StatsFunctions", &Empty);
+
+ if (statsFunctions) {
+ ObjectLock olock(statsFunctions);
+
+ for (const Namespace::Pair& kv : statsFunctions)
+ static_cast<Function::Ptr>(kv.second.Val)->Invoke({ status, perfdata });
+ }
+
+ return std::make_pair(status, perfdata);
+}
+
+REGISTER_STATSFUNCTION(CIB, &CIB::StatsFunc);
+
+void CIB::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata) {
+ double interval = Utility::GetTime() - Application::GetStartTime();
+
+ if (interval > 60)
+ interval = 60;
+
+ status->Set("active_host_checks", GetActiveHostChecksStatistics(interval) / interval);
+ status->Set("passive_host_checks", GetPassiveHostChecksStatistics(interval) / interval);
+ status->Set("active_host_checks_1min", GetActiveHostChecksStatistics(60));
+ status->Set("passive_host_checks_1min", GetPassiveHostChecksStatistics(60));
+ status->Set("active_host_checks_5min", GetActiveHostChecksStatistics(60 * 5));
+ status->Set("passive_host_checks_5min", GetPassiveHostChecksStatistics(60 * 5));
+ status->Set("active_host_checks_15min", GetActiveHostChecksStatistics(60 * 15));
+ status->Set("passive_host_checks_15min", GetPassiveHostChecksStatistics(60 * 15));
+
+ status->Set("active_service_checks", GetActiveServiceChecksStatistics(interval) / interval);
+ status->Set("passive_service_checks", GetPassiveServiceChecksStatistics(interval) / interval);
+ status->Set("active_service_checks_1min", GetActiveServiceChecksStatistics(60));
+ status->Set("passive_service_checks_1min", GetPassiveServiceChecksStatistics(60));
+ status->Set("active_service_checks_5min", GetActiveServiceChecksStatistics(60 * 5));
+ status->Set("passive_service_checks_5min", GetPassiveServiceChecksStatistics(60 * 5));
+ status->Set("active_service_checks_15min", GetActiveServiceChecksStatistics(60 * 15));
+ status->Set("passive_service_checks_15min", GetPassiveServiceChecksStatistics(60 * 15));
+
+ // Checker related stats
+ status->Set("remote_check_queue", ClusterEvents::GetCheckRequestQueueSize());
+ status->Set("current_pending_callbacks", Application::GetTP().GetPending());
+ status->Set("current_concurrent_checks", Checkable::CurrentConcurrentChecks.load());
+
+ CheckableCheckStatistics scs = CalculateServiceCheckStats();
+
+ status->Set("min_latency", scs.min_latency);
+ status->Set("max_latency", scs.max_latency);
+ status->Set("avg_latency", scs.avg_latency);
+ status->Set("min_execution_time", scs.min_execution_time);
+ status->Set("max_execution_time", scs.max_execution_time);
+ status->Set("avg_execution_time", scs.avg_execution_time);
+
+ ServiceStatistics ss = CalculateServiceStats();
+
+ status->Set("num_services_ok", ss.services_ok);
+ status->Set("num_services_warning", ss.services_warning);
+ status->Set("num_services_critical", ss.services_critical);
+ status->Set("num_services_unknown", ss.services_unknown);
+ status->Set("num_services_pending", ss.services_pending);
+ status->Set("num_services_unreachable", ss.services_unreachable);
+ status->Set("num_services_flapping", ss.services_flapping);
+ status->Set("num_services_in_downtime", ss.services_in_downtime);
+ status->Set("num_services_acknowledged", ss.services_acknowledged);
+ status->Set("num_services_handled", ss.services_handled);
+ status->Set("num_services_problem", ss.services_problem);
+
+ double uptime = Application::GetUptime();
+ status->Set("uptime", uptime);
+
+ HostStatistics hs = CalculateHostStats();
+
+ status->Set("num_hosts_up", hs.hosts_up);
+ status->Set("num_hosts_down", hs.hosts_down);
+ status->Set("num_hosts_pending", hs.hosts_pending);
+ status->Set("num_hosts_unreachable", hs.hosts_unreachable);
+ status->Set("num_hosts_flapping", hs.hosts_flapping);
+ status->Set("num_hosts_in_downtime", hs.hosts_in_downtime);
+ status->Set("num_hosts_acknowledged", hs.hosts_acknowledged);
+ status->Set("num_hosts_handled", hs.hosts_handled);
+ status->Set("num_hosts_problem", hs.hosts_problem);
+}
diff --git a/lib/icinga/cib.hpp b/lib/icinga/cib.hpp
new file mode 100644
index 0000000..00461e3
--- /dev/null
+++ b/lib/icinga/cib.hpp
@@ -0,0 +1,91 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CIB_H
+#define CIB_H
+
+#include "icinga/i2-icinga.hpp"
+#include "base/ringbuffer.hpp"
+#include "base/dictionary.hpp"
+#include "base/array.hpp"
+
+namespace icinga
+{
+
+struct CheckableCheckStatistics {
+ double min_latency;
+ double max_latency;
+ double avg_latency;
+ double min_execution_time;
+ double max_execution_time;
+ double avg_execution_time;
+};
+
+struct ServiceStatistics {
+ double services_ok;
+ double services_warning;
+ double services_critical;
+ double services_unknown;
+ double services_pending;
+ double services_unreachable;
+ double services_flapping;
+ double services_in_downtime;
+ double services_acknowledged;
+ double services_handled;
+ double services_problem;
+};
+
+struct HostStatistics {
+ double hosts_up;
+ double hosts_down;
+ double hosts_unreachable;
+ double hosts_pending;
+ double hosts_flapping;
+ double hosts_in_downtime;
+ double hosts_acknowledged;
+ double hosts_handled;
+ double hosts_problem;
+};
+
+/**
+ * Common Information Base class. Holds some statistics (and will likely be
+ * removed/refactored).
+ *
+ * @ingroup icinga
+ */
+class CIB
+{
+public:
+ static void UpdateActiveHostChecksStatistics(long tv, int num);
+ static int GetActiveHostChecksStatistics(long timespan);
+
+ static void UpdateActiveServiceChecksStatistics(long tv, int num);
+ static int GetActiveServiceChecksStatistics(long timespan);
+
+ static void UpdatePassiveHostChecksStatistics(long tv, int num);
+ static int GetPassiveHostChecksStatistics(long timespan);
+
+ static void UpdatePassiveServiceChecksStatistics(long tv, int num);
+ static int GetPassiveServiceChecksStatistics(long timespan);
+
+ static CheckableCheckStatistics CalculateHostCheckStats();
+ static CheckableCheckStatistics CalculateServiceCheckStats();
+ static HostStatistics CalculateHostStats();
+ static ServiceStatistics CalculateServiceStats();
+
+ static std::pair<Dictionary::Ptr, Array::Ptr> GetFeatureStats();
+
+ static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata);
+
+private:
+ CIB();
+
+ static std::mutex m_Mutex;
+ static RingBuffer m_ActiveHostChecksStatistics;
+ static RingBuffer m_PassiveHostChecksStatistics;
+ static RingBuffer m_ActiveServiceChecksStatistics;
+ static RingBuffer m_PassiveServiceChecksStatistics;
+};
+
+}
+
+#endif /* CIB_H */
diff --git a/lib/icinga/clusterevents-check.cpp b/lib/icinga/clusterevents-check.cpp
new file mode 100644
index 0000000..40325b4
--- /dev/null
+++ b/lib/icinga/clusterevents-check.cpp
@@ -0,0 +1,379 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/clusterevents.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "remote/apilistener.hpp"
+#include "base/configuration.hpp"
+#include "base/defer.hpp"
+#include "base/serializer.hpp"
+#include "base/exception.hpp"
+#include <boost/thread/once.hpp>
+#include <thread>
+
+using namespace icinga;
+
+std::mutex ClusterEvents::m_Mutex;
+std::deque<std::function<void ()>> ClusterEvents::m_CheckRequestQueue;
+bool ClusterEvents::m_CheckSchedulerRunning;
+int ClusterEvents::m_ChecksExecutedDuringInterval;
+int ClusterEvents::m_ChecksDroppedDuringInterval;
+Timer::Ptr ClusterEvents::m_LogTimer;
+
+void ClusterEvents::RemoteCheckThreadProc()
+{
+ Utility::SetThreadName("Remote Check Scheduler");
+
+ int maxConcurrentChecks = IcingaApplication::GetInstance()->GetMaxConcurrentChecks();
+
+ std::unique_lock<std::mutex> lock(m_Mutex);
+
+ for(;;) {
+ if (m_CheckRequestQueue.empty())
+ break;
+
+ lock.unlock();
+ Checkable::AquirePendingCheckSlot(maxConcurrentChecks);
+ lock.lock();
+
+ auto callback = m_CheckRequestQueue.front();
+ m_CheckRequestQueue.pop_front();
+ m_ChecksExecutedDuringInterval++;
+ lock.unlock();
+
+ callback();
+ Checkable::DecreasePendingChecks();
+
+ lock.lock();
+ }
+
+ m_CheckSchedulerRunning = false;
+}
+
+void ClusterEvents::EnqueueCheck(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, []() {
+ m_LogTimer = Timer::Create();
+ m_LogTimer->SetInterval(10);
+ m_LogTimer->OnTimerExpired.connect([](const Timer * const&) { LogRemoteCheckQueueInformation(); });
+ m_LogTimer->Start();
+ });
+
+ std::unique_lock<std::mutex> lock(m_Mutex);
+
+ if (m_CheckRequestQueue.size() >= 25000) {
+ m_ChecksDroppedDuringInterval++;
+ return;
+ }
+
+ m_CheckRequestQueue.emplace_back([origin, params]() { ExecuteCheckFromQueue(origin, params); });
+
+ if (!m_CheckSchedulerRunning) {
+ std::thread t(ClusterEvents::RemoteCheckThreadProc);
+ t.detach();
+ m_CheckSchedulerRunning = true;
+ }
+}
+
+static void SendEventExecutedCommand(const Dictionary::Ptr& params, long exitStatus, const String& output,
+ double start, double end, const ApiListener::Ptr& listener, const MessageOrigin::Ptr& origin,
+ const Endpoint::Ptr& sourceEndpoint)
+{
+ Dictionary::Ptr executedParams = new Dictionary();
+ executedParams->Set("execution", params->Get("source"));
+ executedParams->Set("host", params->Get("host"));
+
+ if (params->Contains("service"))
+ executedParams->Set("service", params->Get("service"));
+
+ executedParams->Set("exit", exitStatus);
+ executedParams->Set("output", output);
+ executedParams->Set("start", start);
+ executedParams->Set("end", end);
+
+ if (origin->IsLocal()) {
+ ClusterEvents::ExecutedCommandAPIHandler(origin, executedParams);
+ } else {
+ Dictionary::Ptr executedMessage = new Dictionary();
+ executedMessage->Set("jsonrpc", "2.0");
+ executedMessage->Set("method", "event::ExecutedCommand");
+ executedMessage->Set("params", executedParams);
+
+ listener->SyncSendMessage(sourceEndpoint, executedMessage);
+ }
+}
+
+void ClusterEvents::ExecuteCheckFromQueue(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) {
+
+ Endpoint::Ptr sourceEndpoint;
+
+ if (origin->FromClient) {
+ sourceEndpoint = origin->FromClient->GetEndpoint();
+ } else if (origin->IsLocal()){
+ sourceEndpoint = Endpoint::GetLocalEndpoint();
+ }
+
+ if (!sourceEndpoint || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'execute command' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return;
+ }
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener) {
+ Log(LogCritical, "ApiListener") << "No instance available.";
+ return;
+ }
+
+ Defer resetExecuteCommandProcessFinishedHandler ([]() {
+ Checkable::ExecuteCommandProcessFinishedHandler = nullptr;
+ });
+
+ if (params->Contains("source")) {
+ String uuid = params->Get("source");
+
+ String checkableName = params->Get("host");
+
+ if (params->Contains("service"))
+ checkableName += "!" + params->Get("service");
+
+ /* Check deadline */
+ double deadline = params->Get("deadline");
+
+ if (Utility::GetTime() > deadline) {
+ Log(LogNotice, "ApiListener")
+ << "Discarding 'ExecuteCheckFromQueue' event for checkable '" << checkableName
+ << "' from '" << origin->FromClient->GetIdentity() << "': Deadline has expired.";
+ return;
+ }
+
+ Checkable::ExecuteCommandProcessFinishedHandler = [checkableName, listener, sourceEndpoint, origin, params] (const Value& commandLine, const ProcessResult& pr) {
+ if (params->Get("command_type") == "check_command") {
+ Checkable::CurrentConcurrentChecks.fetch_sub(1);
+ Checkable::DecreasePendingChecks();
+ }
+
+ if (pr.ExitStatus > 3) {
+ Process::Arguments parguments = Process::PrepareCommand(commandLine);
+ Log(LogWarning, "ApiListener")
+ << "Command for object '" << checkableName << "' (PID: " << pr.PID
+ << ", arguments: " << Process::PrettyPrintArguments(parguments) << ") terminated with exit code "
+ << pr.ExitStatus << ", output: " << pr.Output;
+ }
+
+ SendEventExecutedCommand(params, pr.ExitStatus, pr.Output, pr.ExecutionStart, pr.ExecutionEnd, listener,
+ origin, sourceEndpoint);
+ };
+ }
+
+ if (!listener->GetAcceptCommands() && !origin->IsLocal()) {
+ Log(LogWarning, "ApiListener")
+ << "Ignoring command. '" << listener->GetName() << "' does not accept commands.";
+
+ String output = "Endpoint '" + Endpoint::GetLocalEndpoint()->GetName() + "' does not accept commands.";
+
+ if (params->Contains("source")) {
+ double now = Utility::GetTime();
+ SendEventExecutedCommand(params, 126, output, now, now, listener, origin, sourceEndpoint);
+ } else {
+ Host::Ptr host = new Host();
+ Dictionary::Ptr attrs = new Dictionary();
+
+ attrs->Set("__name", params->Get("host"));
+ attrs->Set("type", "Host");
+ attrs->Set("enable_active_checks", false);
+
+ Deserialize(host, attrs, false, FAConfig);
+
+ if (params->Contains("service"))
+ host->SetExtension("agent_service_name", params->Get("service"));
+
+ CheckResult::Ptr cr = new CheckResult();
+ cr->SetState(ServiceUnknown);
+ cr->SetOutput(output);
+
+ Dictionary::Ptr message = MakeCheckResultMessage(host, cr);
+ listener->SyncSendMessage(sourceEndpoint, message);
+ }
+
+ return;
+ }
+
+ /* use a virtual host object for executing the command */
+ Host::Ptr host = new Host();
+ Dictionary::Ptr attrs = new Dictionary();
+
+ attrs->Set("__name", params->Get("host"));
+ attrs->Set("type", "Host");
+
+ /*
+ * Override the check timeout if the parent caller provided the value. Compatible with older versions not
+ * passing this inside the cluster message.
+ * This happens with host/service command_endpoint agents and the 'check_timeout' attribute being specified.
+ */
+ if (params->Contains("check_timeout"))
+ attrs->Set("check_timeout", params->Get("check_timeout"));
+
+ Deserialize(host, attrs, false, FAConfig);
+
+ if (params->Contains("service"))
+ host->SetExtension("agent_service_name", params->Get("service"));
+
+ String command = params->Get("command");
+ String command_type = params->Get("command_type");
+
+ if (command_type == "check_command") {
+ if (!CheckCommand::GetByName(command)) {
+ ServiceState state = ServiceUnknown;
+ String output = "Check command '" + command + "' does not exist.";
+ double now = Utility::GetTime();
+
+ if (params->Contains("source")) {
+ SendEventExecutedCommand(params, state, output, now, now, listener, origin, sourceEndpoint);
+ } else {
+ CheckResult::Ptr cr = new CheckResult();
+ cr->SetState(state);
+ cr->SetOutput(output);
+ Dictionary::Ptr message = MakeCheckResultMessage(host, cr);
+ listener->SyncSendMessage(sourceEndpoint, message);
+ }
+
+ return;
+ }
+ } else if (command_type == "event_command") {
+ if (!EventCommand::GetByName(command)) {
+ String output = "Event command '" + command + "' does not exist.";
+ Log(LogWarning, "ClusterEvents") << output;
+
+ if (params->Contains("source")) {
+ double now = Utility::GetTime();
+ SendEventExecutedCommand(params, ServiceUnknown, output, now, now, listener, origin, sourceEndpoint);
+ }
+
+ return;
+ }
+ } else if (command_type == "notification_command") {
+ if (!NotificationCommand::GetByName(command)) {
+ String output = "Notification command '" + command + "' does not exist.";
+ Log(LogWarning, "ClusterEvents") << output;
+
+ if (params->Contains("source")) {
+ double now = Utility::GetTime();
+ SendEventExecutedCommand(params, ServiceUnknown, output, now, now, listener, origin, sourceEndpoint);
+ }
+
+ return;
+ }
+ }
+
+ attrs->Set(command_type, params->Get("command"));
+ attrs->Set("command_endpoint", sourceEndpoint->GetName());
+
+ Deserialize(host, attrs, false, FAConfig);
+
+ host->SetExtension("agent_check", true);
+
+ Dictionary::Ptr macros = params->Get("macros");
+
+ if (command_type == "check_command") {
+ try {
+ host->ExecuteRemoteCheck(macros);
+ } catch (const std::exception& ex) {
+ String output = "Exception occurred while checking '" + host->GetName() + "': " + DiagnosticInformation(ex);
+ ServiceState state = ServiceUnknown;
+ double now = Utility::GetTime();
+
+ if (params->Contains("source")) {
+ SendEventExecutedCommand(params, state, output, now, now, listener, origin, sourceEndpoint);
+ } else {
+ CheckResult::Ptr cr = new CheckResult();
+ cr->SetState(state);
+ cr->SetOutput(output);
+ cr->SetScheduleStart(now);
+ cr->SetScheduleEnd(now);
+ cr->SetExecutionStart(now);
+ cr->SetExecutionEnd(now);
+
+ Dictionary::Ptr message = MakeCheckResultMessage(host, cr);
+ listener->SyncSendMessage(sourceEndpoint, message);
+ }
+
+ Log(LogCritical, "checker", output);
+ }
+ } else if (command_type == "event_command") {
+ try {
+ host->ExecuteEventHandler(macros, true);
+ } catch (const std::exception& ex) {
+ if (params->Contains("source")) {
+ String output = "Exception occurred while executing event command '" + command + "' for '" +
+ host->GetName() + "': " + DiagnosticInformation(ex);
+
+ double now = Utility::GetTime();
+ SendEventExecutedCommand(params, ServiceUnknown, output, now, now, listener, origin, sourceEndpoint);
+ } else {
+ throw;
+ }
+ }
+ } else if (command_type == "notification_command" && params->Contains("source")) {
+ /* Get user */
+ User::Ptr user = new User();
+ Dictionary::Ptr attrs = new Dictionary();
+ attrs->Set("__name", params->Get("user"));
+ attrs->Set("type", User::GetTypeName());
+
+ Deserialize(user, attrs, false, FAConfig);
+
+ /* Get notification */
+ Notification::Ptr notification = new Notification();
+ attrs->Clear();
+ attrs->Set("__name", params->Get("notification"));
+ attrs->Set("type", Notification::GetTypeName());
+ attrs->Set("command", command);
+
+ Deserialize(notification, attrs, false, FAConfig);
+
+ try {
+ CheckResult::Ptr cr = new CheckResult();
+ String author = macros->Get("notification_author");
+ NotificationCommand::Ptr notificationCommand = NotificationCommand::GetByName(command);
+
+ notificationCommand->Execute(notification, user, cr, NotificationType::NotificationCustom,
+ author, "");
+ } catch (const std::exception& ex) {
+ String output = "Exception occurred during notification '" + notification->GetName()
+ + "' for checkable '" + notification->GetCheckable()->GetName()
+ + "' and user '" + user->GetName() + "' using command '" + command + "': "
+ + DiagnosticInformation(ex, false);
+ double now = Utility::GetTime();
+ SendEventExecutedCommand(params, ServiceUnknown, output, now, now, listener, origin, sourceEndpoint);
+ }
+ }
+}
+
+int ClusterEvents::GetCheckRequestQueueSize()
+{
+ return m_CheckRequestQueue.size();
+}
+
+void ClusterEvents::LogRemoteCheckQueueInformation() {
+ if (m_ChecksDroppedDuringInterval > 0) {
+ Log(LogCritical, "ClusterEvents")
+ << "Remote check queue ran out of slots. "
+ << m_ChecksDroppedDuringInterval << " checks dropped.";
+ m_ChecksDroppedDuringInterval = 0;
+ }
+
+ if (m_ChecksExecutedDuringInterval == 0)
+ return;
+
+ Log(LogInformation, "RemoteCheckQueue")
+ << "items: " << m_CheckRequestQueue.size()
+ << ", rate: " << m_ChecksExecutedDuringInterval / 10 << "/s "
+ << "(" << m_ChecksExecutedDuringInterval * 6 << "/min "
+ << m_ChecksExecutedDuringInterval * 6 * 5 << "/5min "
+ << m_ChecksExecutedDuringInterval * 6 * 15 << "/15min" << ");";
+
+ m_ChecksExecutedDuringInterval = 0;
+}
diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp
new file mode 100644
index 0000000..fe5167b
--- /dev/null
+++ b/lib/icinga/clusterevents.cpp
@@ -0,0 +1,1623 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/clusterevents.hpp"
+#include "icinga/service.hpp"
+#include "remote/apilistener.hpp"
+#include "remote/endpoint.hpp"
+#include "remote/messageorigin.hpp"
+#include "remote/zone.hpp"
+#include "remote/apifunction.hpp"
+#include "remote/eventqueue.hpp"
+#include "base/application.hpp"
+#include "base/configtype.hpp"
+#include "base/utility.hpp"
+#include "base/perfdatavalue.hpp"
+#include "base/exception.hpp"
+#include "base/initialize.hpp"
+#include "base/serializer.hpp"
+#include "base/json.hpp"
+#include <fstream>
+
+using namespace icinga;
+
+INITIALIZE_ONCE(&ClusterEvents::StaticInitialize);
+
+REGISTER_APIFUNCTION(CheckResult, event, &ClusterEvents::CheckResultAPIHandler);
+REGISTER_APIFUNCTION(SetNextCheck, event, &ClusterEvents::NextCheckChangedAPIHandler);
+REGISTER_APIFUNCTION(SetLastCheckStarted, event, &ClusterEvents::LastCheckStartedChangedAPIHandler);
+REGISTER_APIFUNCTION(SetStateBeforeSuppression, event, &ClusterEvents::StateBeforeSuppressionChangedAPIHandler);
+REGISTER_APIFUNCTION(SetSuppressedNotifications, event, &ClusterEvents::SuppressedNotificationsChangedAPIHandler);
+REGISTER_APIFUNCTION(SetSuppressedNotificationTypes, event, &ClusterEvents::SuppressedNotificationTypesChangedAPIHandler);
+REGISTER_APIFUNCTION(SetNextNotification, event, &ClusterEvents::NextNotificationChangedAPIHandler);
+REGISTER_APIFUNCTION(UpdateLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler);
+REGISTER_APIFUNCTION(ClearLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserClearedAPIHandler);
+REGISTER_APIFUNCTION(SetForceNextCheck, event, &ClusterEvents::ForceNextCheckChangedAPIHandler);
+REGISTER_APIFUNCTION(SetForceNextNotification, event, &ClusterEvents::ForceNextNotificationChangedAPIHandler);
+REGISTER_APIFUNCTION(SetAcknowledgement, event, &ClusterEvents::AcknowledgementSetAPIHandler);
+REGISTER_APIFUNCTION(ClearAcknowledgement, event, &ClusterEvents::AcknowledgementClearedAPIHandler);
+REGISTER_APIFUNCTION(ExecuteCommand, event, &ClusterEvents::ExecuteCommandAPIHandler);
+REGISTER_APIFUNCTION(SendNotifications, event, &ClusterEvents::SendNotificationsAPIHandler);
+REGISTER_APIFUNCTION(NotificationSentUser, event, &ClusterEvents::NotificationSentUserAPIHandler);
+REGISTER_APIFUNCTION(NotificationSentToAllUsers, event, &ClusterEvents::NotificationSentToAllUsersAPIHandler);
+REGISTER_APIFUNCTION(ExecutedCommand, event, &ClusterEvents::ExecutedCommandAPIHandler);
+REGISTER_APIFUNCTION(UpdateExecutions, event, &ClusterEvents::UpdateExecutionsAPIHandler);
+REGISTER_APIFUNCTION(SetRemovalInfo, event, &ClusterEvents::SetRemovalInfoAPIHandler);
+
+void ClusterEvents::StaticInitialize()
+{
+ Checkable::OnNewCheckResult.connect(&ClusterEvents::CheckResultHandler);
+ Checkable::OnNextCheckChanged.connect(&ClusterEvents::NextCheckChangedHandler);
+ Checkable::OnLastCheckStartedChanged.connect(&ClusterEvents::LastCheckStartedChangedHandler);
+ Checkable::OnStateBeforeSuppressionChanged.connect(&ClusterEvents::StateBeforeSuppressionChangedHandler);
+ Checkable::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationsChangedHandler);
+ Notification::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationTypesChangedHandler);
+ Notification::OnNextNotificationChanged.connect(&ClusterEvents::NextNotificationChangedHandler);
+ Notification::OnLastNotifiedStatePerUserUpdated.connect(&ClusterEvents::LastNotifiedStatePerUserUpdatedHandler);
+ Notification::OnLastNotifiedStatePerUserCleared.connect(&ClusterEvents::LastNotifiedStatePerUserClearedHandler);
+ Checkable::OnForceNextCheckChanged.connect(&ClusterEvents::ForceNextCheckChangedHandler);
+ Checkable::OnForceNextNotificationChanged.connect(&ClusterEvents::ForceNextNotificationChangedHandler);
+ Checkable::OnNotificationsRequested.connect(&ClusterEvents::SendNotificationsHandler);
+ Checkable::OnNotificationSentToUser.connect(&ClusterEvents::NotificationSentUserHandler);
+ Checkable::OnNotificationSentToAllUsers.connect(&ClusterEvents::NotificationSentToAllUsersHandler);
+
+ Checkable::OnAcknowledgementSet.connect(&ClusterEvents::AcknowledgementSetHandler);
+ Checkable::OnAcknowledgementCleared.connect(&ClusterEvents::AcknowledgementClearedHandler);
+
+ Comment::OnRemovalInfoChanged.connect(&ClusterEvents::SetRemovalInfoHandler);
+ Downtime::OnRemovalInfoChanged.connect(&ClusterEvents::SetRemovalInfoHandler);
+}
+
+Dictionary::Ptr ClusterEvents::MakeCheckResultMessage(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
+{
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::CheckResult");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ else {
+ Value agent_service_name = checkable->GetExtension("agent_service_name");
+
+ if (!agent_service_name.IsEmpty())
+ params->Set("service", agent_service_name);
+ }
+ params->Set("cr", Serialize(cr));
+
+ message->Set("params", params);
+
+ return message;
+}
+
+void ClusterEvents::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Dictionary::Ptr message = MakeCheckResultMessage(checkable, cr);
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::CheckResultAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'check result' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ CheckResult::Ptr cr;
+ Array::Ptr vperf;
+
+ if (params->Contains("cr")) {
+ cr = new CheckResult();
+ Dictionary::Ptr vcr = params->Get("cr");
+
+ if (vcr && vcr->Contains("performance_data")) {
+ vperf = vcr->Get("performance_data");
+
+ if (vperf)
+ vcr->Remove("performance_data");
+
+ Deserialize(cr, vcr, true);
+ }
+ }
+
+ if (!cr)
+ return Empty;
+
+ ArrayData rperf;
+
+ if (vperf) {
+ ObjectLock olock(vperf);
+ for (const Value& vp : vperf) {
+ Value p;
+
+ if (vp.IsObjectType<Dictionary>()) {
+ PerfdataValue::Ptr val = new PerfdataValue();
+ Deserialize(val, vp, true);
+ rperf.push_back(val);
+ } else
+ rperf.push_back(vp);
+ }
+ }
+
+ cr->SetPerformanceData(new Array(std::move(rperf)));
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable) && endpoint != checkable->GetCommandEndpoint()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'check result' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ if (!checkable->IsPaused() && Zone::GetLocalZone() == checkable->GetZone() && endpoint == checkable->GetCommandEndpoint())
+ checkable->ProcessCheckResult(cr);
+ else
+ checkable->ProcessCheckResult(cr, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::NextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("next_check", checkable->GetNextCheck());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetNextCheck");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::NextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'next check changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'next check changed' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ double nextCheck = params->Get("next_check");
+
+ if (nextCheck < Application::GetStartTime() + 60)
+ return Empty;
+
+ checkable->SetNextCheck(params->Get("next_check"), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::LastCheckStartedChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("last_check_started", checkable->GetLastCheckStarted());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetLastCheckStarted");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::LastCheckStartedChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'last_check_started changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'last_check_started changed' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ checkable->SetLastCheckStarted(params->Get("last_check_started"), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::StateBeforeSuppressionChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("state_before_suppression", checkable->GetStateBeforeSuppression());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetStateBeforeSuppression");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, nullptr, message, true);
+}
+
+Value ClusterEvents::StateBeforeSuppressionChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'state before suppression changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'state before suppression changed' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ checkable->SetStateBeforeSuppression(ServiceState(int(params->Get("state_before_suppression"))), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::SuppressedNotificationsChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("suppressed_notifications", checkable->GetSuppressedNotifications());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetSuppressedNotifications");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, nullptr, message, true);
+}
+
+Value ClusterEvents::SuppressedNotificationsChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'suppressed notifications changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'suppressed notifications changed' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ checkable->SetSuppressedNotifications(params->Get("suppressed_notifications"), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::SuppressedNotificationTypesChangedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("notification", notification->GetName());
+ params->Set("suppressed_notifications", notification->GetSuppressedNotifications());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetSuppressedNotificationTypes");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, nullptr, message, true);
+}
+
+Value ClusterEvents::SuppressedNotificationTypesChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'suppressed notifications changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ auto notification (Notification::GetByName(params->Get("notification")));
+
+ if (!notification)
+ return Empty;
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'suppressed notification types changed' message for notification '" << notification->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ notification->SetSuppressedNotifications(params->Get("suppressed_notifications"), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::NextNotificationChangedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("notification", notification->GetName());
+ params->Set("next_notification", notification->GetNextNotification());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetNextNotification");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, notification, message, true);
+}
+
+Value ClusterEvents::NextNotificationChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'next notification changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Notification::Ptr notification = Notification::GetByName(params->Get("notification"));
+
+ if (!notification)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(notification)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'next notification changed' message for notification '" << notification->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ double nextNotification = params->Get("next_notification");
+
+ if (nextNotification < Utility::GetTime())
+ return Empty;
+
+ notification->SetNextNotification(nextNotification, false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::LastNotifiedStatePerUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin)
+{
+ auto listener (ApiListener::GetInstance());
+
+ if (!listener) {
+ return;
+ }
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("notification", notification->GetName());
+ params->Set("user", user);
+ params->Set("state", state);
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::UpdateLastNotifiedStatePerUser");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, notification, message, true);
+}
+
+Value ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ auto endpoint (origin->FromClient->GetEndpoint());
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'last notified state of user updated' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+
+ return Empty;
+ }
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'last notified state of user updated' message from '"
+ << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+
+ return Empty;
+ }
+
+ auto notification (Notification::GetByName(params->Get("notification")));
+
+ if (!notification) {
+ return Empty;
+ }
+
+ auto state (params->Get("state"));
+
+ if (!state.IsNumber()) {
+ return Empty;
+ }
+
+ notification->GetLastNotifiedStatePerUser()->Set(params->Get("user"), state);
+ Notification::OnLastNotifiedStatePerUserUpdated(notification, params->Get("user"), state, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::LastNotifiedStatePerUserClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin)
+{
+ auto listener (ApiListener::GetInstance());
+
+ if (!listener) {
+ return;
+ }
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("notification", notification->GetName());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::ClearLastNotifiedStatePerUser");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, notification, message, true);
+}
+
+Value ClusterEvents::LastNotifiedStatePerUserClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ auto endpoint (origin->FromClient->GetEndpoint());
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'last notified state of user cleared' message from '"
+ << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+
+ return Empty;
+ }
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'last notified state of user cleared' message from '"
+ << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+
+ return Empty;
+ }
+
+ auto notification (Notification::GetByName(params->Get("notification")));
+
+ if (!notification) {
+ return Empty;
+ }
+
+ notification->GetLastNotifiedStatePerUser()->Clear();
+ Notification::OnLastNotifiedStatePerUserCleared(notification, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("forced", checkable->GetForceNextCheck());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetForceNextCheck");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::ForceNextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'force next check changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'force next check' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ checkable->SetForceNextCheck(params->Get("forced"), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::ForceNextNotificationChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("forced", checkable->GetForceNextNotification());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetForceNextNotification");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::ForceNextNotificationChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'force next notification changed' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'force next notification' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ checkable->SetForceNextNotification(params->Get("forced"), false, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::AcknowledgementSetHandler(const Checkable::Ptr& checkable,
+ const String& author, const String& comment, AcknowledgementType type,
+ bool notify, bool persistent, double changeTime, double expiry, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("author", author);
+ params->Set("comment", comment);
+ params->Set("acktype", type);
+ params->Set("notify", notify);
+ params->Set("persistent", persistent);
+ params->Set("expiry", expiry);
+ params->Set("change_time", changeTime);
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetAcknowledgement");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::AcknowledgementSetAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'acknowledgement set' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'acknowledgement set' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ ObjectLock oLock (checkable);
+
+ if (checkable->IsAcknowledged()) {
+ Log(LogWarning, "ClusterEvents")
+ << "Discarding 'acknowledgement set' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Checkable is already acknowledged.";
+ return Empty;
+ }
+
+ checkable->AcknowledgeProblem(params->Get("author"), params->Get("comment"),
+ static_cast<AcknowledgementType>(static_cast<int>(params->Get("acktype"))),
+ params->Get("notify"), params->Get("persistent"), params->Get("change_time"), params->Get("expiry"), origin);
+
+ return Empty;
+}
+
+void ClusterEvents::AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double changeTime, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("author", removedBy);
+ params->Set("change_time", changeTime);
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::ClearAcknowledgement");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, message, true);
+}
+
+Value ClusterEvents::AcknowledgementClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'acknowledgement cleared' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'acknowledgement cleared' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ checkable->ClearAcknowledgement(params->Get("author"), params->Get("change_time"), origin);
+
+ return Empty;
+}
+
+Value ClusterEvents::ExecuteCommandAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return Empty;
+
+ if (!origin->IsLocal()) {
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ /* Discard messages from anonymous clients */
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents") << "Discarding 'execute command' message from '"
+ << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Zone::Ptr originZone = endpoint->GetZone();
+
+ Zone::Ptr localZone = Zone::GetLocalZone();
+ bool fromLocalZone = originZone == localZone;
+
+ Zone::Ptr parentZone = localZone->GetParent();
+ bool fromParentZone = parentZone && originZone == parentZone;
+
+ if (!fromLocalZone && !fromParentZone) {
+ Log(LogNotice, "ClusterEvents") << "Discarding 'execute command' message from '"
+ << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+ }
+
+ String executionUuid = params->Get("source");
+
+ if (params->Contains("endpoint")) {
+ Endpoint::Ptr execEndpoint = Endpoint::GetByName(params->Get("endpoint"));
+
+ if (!execEndpoint) {
+ Log(LogWarning, "ClusterEvents")
+ << "Discarding 'execute command' message " << executionUuid
+ << ": Endpoint " << params->Get("endpoint") << " does not exist";
+ return Empty;
+ }
+
+ if (execEndpoint != Endpoint::GetLocalEndpoint()) {
+ Zone::Ptr endpointZone = execEndpoint->GetZone();
+ Zone::Ptr localZone = Zone::GetLocalZone();
+
+ if (!endpointZone->IsChildOf(localZone)) {
+ return Empty;
+ }
+
+ /* Check if the child endpoints have Icinga version >= 2.13 */
+ for (const Zone::Ptr &zone : ConfigType::GetObjectsByType<Zone>()) {
+ /* Fetch immediate child zone members */
+ if (zone->GetParent() == localZone && zone->CanAccessObject(endpointZone)) {
+ std::set<Endpoint::Ptr> endpoints = zone->GetEndpoints();
+
+ for (const Endpoint::Ptr &childEndpoint : endpoints) {
+ if (!(childEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand)) {
+ double now = Utility::GetTime();
+ Dictionary::Ptr executedParams = new Dictionary();
+ executedParams->Set("execution", executionUuid);
+ executedParams->Set("host", params->Get("host"));
+
+ if (params->Contains("service"))
+ executedParams->Set("service", params->Get("service"));
+
+ executedParams->Set("exit", 126);
+ executedParams->Set("output",
+ "Endpoint '" + childEndpoint->GetName() + "' doesn't support executing arbitrary commands.");
+ executedParams->Set("start", now);
+ executedParams->Set("end", now);
+
+ Dictionary::Ptr executedMessage = new Dictionary();
+ executedMessage->Set("jsonrpc", "2.0");
+ executedMessage->Set("method", "event::ExecutedCommand");
+ executedMessage->Set("params", executedParams);
+
+ listener->RelayMessage(nullptr, nullptr, executedMessage, true);
+ return Empty;
+ }
+ }
+
+ Checkable::Ptr checkable;
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+ if (!host) {
+ Log(LogWarning, "ClusterEvents")
+ << "Discarding 'execute command' message " << executionUuid
+ << ": host " << params->Get("host") << " does not exist";
+ return Empty;
+ }
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable) {
+ String checkableName = host->GetName();
+ if (params->Contains("service"))
+ checkableName += "!" + params->Get("service");
+
+ Log(LogWarning, "ClusterEvents")
+ << "Discarding 'execute command' message " << executionUuid
+ << ": " << checkableName << " does not exist";
+ return Empty;
+ }
+
+ /* Return an error when the endpointZone is different than the child zone and
+ * the child zone can't access the checkable.
+ * The zones are checked to allow for the case where command_endpoint is specified in the checkable
+ * but checkable is not actually present in the agent.
+ */
+ if (!zone->CanAccessObject(checkable) && zone != endpointZone) {
+ double now = Utility::GetTime();
+ Dictionary::Ptr executedParams = new Dictionary();
+ executedParams->Set("execution", executionUuid);
+ executedParams->Set("host", params->Get("host"));
+
+ if (params->Contains("service"))
+ executedParams->Set("service", params->Get("service"));
+
+ executedParams->Set("exit", 126);
+ executedParams->Set(
+ "output",
+ "Zone '" + zone->GetName() + "' cannot access to checkable '" + checkable->GetName() + "'."
+ );
+ executedParams->Set("start", now);
+ executedParams->Set("end", now);
+
+ Dictionary::Ptr executedMessage = new Dictionary();
+ executedMessage->Set("jsonrpc", "2.0");
+ executedMessage->Set("method", "event::ExecutedCommand");
+ executedMessage->Set("params", executedParams);
+
+ listener->RelayMessage(nullptr, nullptr, executedMessage, true);
+ return Empty;
+ }
+ }
+ }
+
+ Dictionary::Ptr execMessage = new Dictionary();
+ execMessage->Set("jsonrpc", "2.0");
+ execMessage->Set("method", "event::ExecuteCommand");
+ execMessage->Set("params", params);
+
+ listener->RelayMessage(origin, endpointZone, execMessage, true);
+ return Empty;
+ }
+ }
+
+ EnqueueCheck(origin, params);
+
+ return Empty;
+}
+
+void ClusterEvents::SendNotificationsHandler(const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr& cr, const String& author, const String& text, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Dictionary::Ptr message = MakeCheckResultMessage(checkable, cr);
+ message->Set("method", "event::SendNotifications");
+
+ Dictionary::Ptr params = message->Get("params");
+ params->Set("type", type);
+ params->Set("author", author);
+ params->Set("text", text);
+
+ listener->RelayMessage(origin, nullptr, message, true);
+}
+
+Value ClusterEvents::SendNotificationsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'send notification' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'send custom notification' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ CheckResult::Ptr cr;
+ Array::Ptr vperf;
+
+ if (params->Contains("cr")) {
+ cr = new CheckResult();
+ Dictionary::Ptr vcr = params->Get("cr");
+
+ if (vcr && vcr->Contains("performance_data")) {
+ vperf = vcr->Get("performance_data");
+
+ if (vperf)
+ vcr->Remove("performance_data");
+
+ Deserialize(cr, vcr, true);
+ }
+ }
+
+ NotificationType type = static_cast<NotificationType>(static_cast<int>(params->Get("type")));
+ String author = params->Get("author");
+ String text = params->Get("text");
+
+ Checkable::OnNotificationsRequested(checkable, type, cr, author, text, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::NotificationSentUserHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, const User::Ptr& user,
+ NotificationType notificationType, const CheckResult::Ptr& cr, const String& author, const String& commentText, const String& command,
+ const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("notification", notification->GetName());
+ params->Set("user", user->GetName());
+ params->Set("type", notificationType);
+ params->Set("cr", Serialize(cr));
+ params->Set("author", author);
+ params->Set("text", commentText);
+ params->Set("command", command);
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::NotificationSentUser");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, nullptr, message, true);
+}
+
+Value ClusterEvents::NotificationSentUserAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'sent notification to user' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'send notification to user' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ CheckResult::Ptr cr;
+ Array::Ptr vperf;
+
+ if (params->Contains("cr")) {
+ cr = new CheckResult();
+ Dictionary::Ptr vcr = params->Get("cr");
+
+ if (vcr && vcr->Contains("performance_data")) {
+ vperf = vcr->Get("performance_data");
+
+ if (vperf)
+ vcr->Remove("performance_data");
+
+ Deserialize(cr, vcr, true);
+ }
+ }
+
+ NotificationType type = static_cast<NotificationType>(static_cast<int>(params->Get("type")));
+ String author = params->Get("author");
+ String text = params->Get("text");
+
+ Notification::Ptr notification = Notification::GetByName(params->Get("notification"));
+
+ if (!notification)
+ return Empty;
+
+ User::Ptr user = User::GetByName(params->Get("user"));
+
+ if (!user)
+ return Empty;
+
+ String command = params->Get("command");
+
+ Checkable::OnNotificationSentToUser(notification, checkable, user, type, cr, author, text, command, origin);
+
+ return Empty;
+}
+
+void ClusterEvents::NotificationSentToAllUsersHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users,
+ NotificationType notificationType, const CheckResult::Ptr& cr, const String& author, const String& commentText, const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("host", host->GetName());
+ if (service)
+ params->Set("service", service->GetShortName());
+ params->Set("notification", notification->GetName());
+
+ ArrayData ausers;
+ for (const User::Ptr& user : users) {
+ ausers.push_back(user->GetName());
+ }
+ params->Set("users", new Array(std::move(ausers)));
+
+ params->Set("type", notificationType);
+ params->Set("cr", Serialize(cr));
+ params->Set("author", author);
+ params->Set("text", commentText);
+
+ params->Set("last_notification", notification->GetLastNotification());
+ params->Set("next_notification", notification->GetNextNotification());
+ params->Set("notification_number", notification->GetNotificationNumber());
+ params->Set("last_problem_notification", notification->GetLastProblemNotification());
+ params->Set("no_more_notifications", notification->GetNoMoreNotifications());
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::NotificationSentToAllUsers");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, nullptr, message, true);
+}
+
+Value ClusterEvents::NotificationSentToAllUsersAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'sent notification to all users' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'sent notification to all users' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ CheckResult::Ptr cr;
+ Array::Ptr vperf;
+
+ if (params->Contains("cr")) {
+ cr = new CheckResult();
+ Dictionary::Ptr vcr = params->Get("cr");
+
+ if (vcr && vcr->Contains("performance_data")) {
+ vperf = vcr->Get("performance_data");
+
+ if (vperf)
+ vcr->Remove("performance_data");
+
+ Deserialize(cr, vcr, true);
+ }
+ }
+
+ NotificationType type = static_cast<NotificationType>(static_cast<int>(params->Get("type")));
+ String author = params->Get("author");
+ String text = params->Get("text");
+
+ Notification::Ptr notification = Notification::GetByName(params->Get("notification"));
+
+ if (!notification)
+ return Empty;
+
+ Array::Ptr ausers = params->Get("users");
+
+ if (!ausers)
+ return Empty;
+
+ std::set<User::Ptr> users;
+
+ {
+ ObjectLock olock(ausers);
+ for (const String& auser : ausers) {
+ User::Ptr user = User::GetByName(auser);
+
+ if (!user)
+ continue;
+
+ users.insert(user);
+ }
+ }
+
+ notification->SetLastNotification(params->Get("last_notification"));
+ notification->SetNextNotification(params->Get("next_notification"));
+ notification->SetNotificationNumber(params->Get("notification_number"));
+ notification->SetLastProblemNotification(params->Get("last_problem_notification"));
+ notification->SetNoMoreNotifications(params->Get("no_more_notifications"));
+
+ ArrayData notifiedProblemUsers;
+ for (const User::Ptr& user : users) {
+ notifiedProblemUsers.push_back(user->GetName());
+ }
+
+ notification->SetNotifiedProblemUsers(new Array(std::move(notifiedProblemUsers)));
+
+ Checkable::OnNotificationSentToAllUsers(notification, checkable, users, type, cr, author, text, origin);
+
+ return Empty;
+}
+
+Value ClusterEvents::ExecutedCommandAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return Empty;
+
+ Endpoint::Ptr endpoint;
+
+ if (origin->FromClient) {
+ endpoint = origin->FromClient->GetEndpoint();
+ } else if (origin->IsLocal()) {
+ endpoint = Endpoint::GetLocalEndpoint();
+ }
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message from '" << origin->FromClient->GetIdentity()
+ << "': Invalid endpoint origin (client not allowed).";
+
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ ObjectLock oLock (checkable);
+
+ if (!params->Contains("execution")) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Execution UUID missing.";
+ return Empty;
+ }
+
+ String uuid = params->Get("execution");
+
+ Dictionary::Ptr executions = checkable->GetExecutions();
+
+ if (!executions) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Execution '" << uuid << "' missing.";
+ return Empty;
+ }
+
+ Dictionary::Ptr execution = executions->Get(uuid);
+
+ if (!execution) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Execution '" << uuid << "' missing.";
+ return Empty;
+ }
+
+ Endpoint::Ptr command_endpoint = Endpoint::GetByName(execution->Get("endpoint"));
+ if (!command_endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message from '" << origin->FromClient->GetIdentity()
+ << "': Command endpoint does not exists.";
+
+ return Empty;
+ }
+
+ if (origin->FromZone && !command_endpoint->GetZone()->IsChildOf(origin->FromZone)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ if (params->Contains("exit"))
+ execution->Set("exit", params->Get("exit"));
+
+ if (params->Contains("output"))
+ execution->Set("output", params->Get("output"));
+
+ if (params->Contains("start"))
+ execution->Set("start", params->Get("start"));
+
+ if (params->Contains("end"))
+ execution->Set("end", params->Get("end"));
+
+ execution->Remove("pending");
+
+ /* Broadcast the update */
+ Dictionary::Ptr executionsToBroadcast = new Dictionary();
+ executionsToBroadcast->Set(uuid, execution);
+ Dictionary::Ptr updateParams = new Dictionary();
+ updateParams->Set("host", host->GetName());
+
+ if (params->Contains("service"))
+ updateParams->Set("service", params->Get("service"));
+
+ updateParams->Set("executions", executionsToBroadcast);
+
+ Dictionary::Ptr updateMessage = new Dictionary();
+ updateMessage->Set("jsonrpc", "2.0");
+ updateMessage->Set("method", "event::UpdateExecutions");
+ updateMessage->Set("params", updateParams);
+
+ listener->RelayMessage(nullptr, checkable, updateMessage, true);
+
+ return Empty;
+}
+
+Value ClusterEvents::UpdateExecutionsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message from '" << origin->FromClient->GetIdentity()
+ << "': Invalid endpoint origin (client not allowed).";
+
+ return Empty;
+ }
+
+ Host::Ptr host = Host::GetByName(params->Get("host"));
+
+ if (!host)
+ return Empty;
+
+ Checkable::Ptr checkable;
+
+ if (params->Contains("service"))
+ checkable = host->GetServiceByShortName(params->Get("service"));
+ else
+ checkable = host;
+
+ if (!checkable)
+ return Empty;
+
+ ObjectLock oLock (checkable);
+
+ if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
+ << "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
+ return Empty;
+ }
+
+ Dictionary::Ptr executions = checkable->GetExecutions();
+
+ if (!executions)
+ executions = new Dictionary();
+
+ Dictionary::Ptr newExecutions = params->Get("executions");
+ newExecutions->CopyTo(executions);
+ checkable->SetExecutions(executions);
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return Empty;
+
+ Dictionary::Ptr updateMessage = new Dictionary();
+ updateMessage->Set("jsonrpc", "2.0");
+ updateMessage->Set("method", "event::UpdateExecutions");
+ updateMessage->Set("params", params);
+
+ listener->RelayMessage(origin, checkable, updateMessage, true);
+
+ return Empty;
+}
+
+void ClusterEvents::SetRemovalInfoHandler(const ConfigObject::Ptr& obj, const String& removedBy, double removeTime,
+ const MessageOrigin::Ptr& origin)
+{
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (!listener)
+ return;
+
+ Dictionary::Ptr params = new Dictionary();
+ params->Set("object_type", obj->GetReflectionType()->GetName());
+ params->Set("object_name", obj->GetName());
+ params->Set("removed_by", removedBy);
+ params->Set("remove_time", removeTime);
+
+ Dictionary::Ptr message = new Dictionary();
+ message->Set("jsonrpc", "2.0");
+ message->Set("method", "event::SetRemovalInfo");
+ message->Set("params", params);
+
+ listener->RelayMessage(origin, obj, message, true);
+}
+
+Value ClusterEvents::SetRemovalInfoAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
+{
+ Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
+
+ if (!endpoint || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'set removal info' message from '" << origin->FromClient->GetIdentity()
+ << "': Invalid endpoint origin (client not allowed).";
+ return Empty;
+ }
+
+ String objectType = params->Get("object_type");
+ String objectName = params->Get("object_name");
+ String removedBy = params->Get("removed_by");
+ double removeTime = params->Get("remove_time");
+
+ if (objectType == Comment::GetTypeName()) {
+ Comment::Ptr comment = Comment::GetByName(objectName);
+
+ if (comment) {
+ comment->SetRemovalInfo(removedBy, removeTime, origin);
+ }
+ } else if (objectType == Downtime::GetTypeName()) {
+ Downtime::Ptr downtime = Downtime::GetByName(objectName);
+
+ if (downtime) {
+ downtime->SetRemovalInfo(removedBy, removeTime, origin);
+ }
+ } else {
+ Log(LogNotice, "ClusterEvents")
+ << "Discarding 'set removal info' message from '" << origin->FromClient->GetIdentity()
+ << "': Unknown object type.";
+ }
+
+ return Empty;
+}
diff --git a/lib/icinga/clusterevents.hpp b/lib/icinga/clusterevents.hpp
new file mode 100644
index 0000000..8daf86a
--- /dev/null
+++ b/lib/icinga/clusterevents.hpp
@@ -0,0 +1,102 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CLUSTEREVENTS_H
+#define CLUSTEREVENTS_H
+
+#include "icinga/checkable.hpp"
+#include "icinga/host.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup icinga
+ */
+class ClusterEvents
+{
+public:
+ static void StaticInitialize();
+
+ static void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin);
+ static Value CheckResultAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void NextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+ static Value NextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void LastCheckStartedChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+ static Value LastCheckStartedChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void StateBeforeSuppressionChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+ static Value StateBeforeSuppressionChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void SuppressedNotificationsChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+ static Value SuppressedNotificationsChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void SuppressedNotificationTypesChangedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin);
+ static Value SuppressedNotificationTypesChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void NextNotificationChangedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin);
+ static Value NextNotificationChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void LastNotifiedStatePerUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin);
+ static Value LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void LastNotifiedStatePerUserClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin);
+ static Value LastNotifiedStatePerUserClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+ static Value ForceNextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void ForceNextNotificationChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
+ static Value ForceNextNotificationChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type,
+ bool notify, bool persistent, double changeTime, double expiry, const MessageOrigin::Ptr& origin);
+ static Value AcknowledgementSetAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double changeTime, const MessageOrigin::Ptr& origin);
+ static Value AcknowledgementClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static Value ExecuteCommandAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static Dictionary::Ptr MakeCheckResultMessage(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);
+
+ static void SendNotificationsHandler(const Checkable::Ptr& checkable, NotificationType type,
+ const CheckResult::Ptr& cr, const String& author, const String& text, const MessageOrigin::Ptr& origin);
+ static Value SendNotificationsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void NotificationSentUserHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, const User::Ptr& user,
+ NotificationType notificationType, const CheckResult::Ptr& cr, const String& author, const String& commentText, const String& command, const MessageOrigin::Ptr& origin);
+ static Value NotificationSentUserAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void NotificationSentToAllUsersHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users,
+ NotificationType notificationType, const CheckResult::Ptr& cr, const String& author, const String& commentText, const MessageOrigin::Ptr& origin);
+ static Value NotificationSentToAllUsersAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+ static Value ExecutedCommandAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+ static Value UpdateExecutionsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static void SetRemovalInfoHandler(const ConfigObject::Ptr& obj, const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin);
+ static Value SetRemovalInfoAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+
+ static int GetCheckRequestQueueSize();
+ static void LogRemoteCheckQueueInformation();
+
+private:
+ static std::mutex m_Mutex;
+ static std::deque<std::function<void ()>> m_CheckRequestQueue;
+ static bool m_CheckSchedulerRunning;
+ static int m_ChecksExecutedDuringInterval;
+ static int m_ChecksDroppedDuringInterval;
+ static Timer::Ptr m_LogTimer;
+
+ static void RemoteCheckThreadProc();
+ static void EnqueueCheck(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+ static void ExecuteCheckFromQueue(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
+};
+
+}
+
+#endif /* CLUSTEREVENTS_H */
diff --git a/lib/icinga/command.cpp b/lib/icinga/command.cpp
new file mode 100644
index 0000000..8e0f357
--- /dev/null
+++ b/lib/icinga/command.cpp
@@ -0,0 +1,68 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/command.hpp"
+#include "icinga/command-ti.cpp"
+#include "icinga/macroprocessor.hpp"
+#include "base/exception.hpp"
+#include "base/objectlock.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(Command);
+
+void Command::Validate(int types, const ValidationUtils& utils)
+{
+ ObjectImpl<Command>::Validate(types, utils);
+
+ Dictionary::Ptr arguments = GetArguments();
+
+ if (!(types & FAConfig))
+ return;
+
+ if (arguments) {
+ if (!GetCommandLine().IsObjectType<Array>())
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "command" }, "Attribute 'command' must be an array if the 'arguments' attribute is set."));
+
+ ObjectLock olock(arguments);
+ for (const Dictionary::Pair& kv : arguments) {
+ const Value& arginfo = kv.second;
+ Value argval;
+
+ if (arginfo.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr argdict = arginfo;
+
+ if (argdict->Contains("value")) {
+ Value argvalue = argdict->Get("value");
+
+ if (argvalue.IsString() && !MacroProcessor::ValidateMacroString(argvalue))
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "arguments", kv.first, "value" }, "Validation failed: Closing $ not found in macro format string '" + argvalue + "'."));
+ }
+
+ if (argdict->Contains("set_if")) {
+ Value argsetif = argdict->Get("set_if");
+
+ if (argsetif.IsString() && !MacroProcessor::ValidateMacroString(argsetif))
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "arguments", kv.first, "set_if" }, "Closing $ not found in macro format string '" + argsetif + "'."));
+ }
+ } else if (arginfo.IsString()) {
+ if (!MacroProcessor::ValidateMacroString(arginfo))
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "arguments", kv.first }, "Closing $ not found in macro format string '" + arginfo + "'."));
+ }
+ }
+ }
+
+ Dictionary::Ptr env = GetEnv();
+
+ if (env) {
+ ObjectLock olock(env);
+ for (const Dictionary::Pair& kv : env) {
+ const Value& envval = kv.second;
+
+ if (!envval.IsString() || envval.IsEmpty())
+ continue;
+
+ if (!MacroProcessor::ValidateMacroString(envval))
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "env", kv.first }, "Closing $ not found in macro format string '" + envval + "'."));
+ }
+ }
+}
diff --git a/lib/icinga/command.hpp b/lib/icinga/command.hpp
new file mode 100644
index 0000000..19bb050
--- /dev/null
+++ b/lib/icinga/command.hpp
@@ -0,0 +1,30 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/command-ti.hpp"
+#include "remote/messageorigin.hpp"
+
+namespace icinga
+{
+
+/**
+ * A command.
+ *
+ * @ingroup icinga
+ */
+class Command : public ObjectImpl<Command>
+{
+public:
+ DECLARE_OBJECT(Command);
+
+ //virtual Dictionary::Ptr Execute(const Object::Ptr& context) = 0;
+
+ void Validate(int types, const ValidationUtils& utils) override;
+};
+
+}
+
+#endif /* COMMAND_H */
diff --git a/lib/icinga/command.ti b/lib/icinga/command.ti
new file mode 100644
index 0000000..2275955
--- /dev/null
+++ b/lib/icinga/command.ti
@@ -0,0 +1,54 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#include "base/function.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+abstract class Command : CustomVarObject
+{
+ [config] Value command (CommandLine);
+ [config, signal_with_old_value] Value arguments;
+ [config] int timeout {
+ default {{{ return 60; }}}
+ };
+ [config, signal_with_old_value] Dictionary::Ptr env;
+ [config, required] Function::Ptr execute;
+};
+
+validator Command {
+ String command;
+ Function command;
+ Array command {
+ String "*";
+ Function "*";
+ };
+
+ Dictionary arguments {
+ String "*";
+ Function "*";
+ Dictionary "*" {
+ String key;
+ String value;
+ Function value;
+ String description;
+ Number "required";
+ Number skip_key;
+ Number repeat_key;
+ String set_if;
+ Function set_if;
+ Number order;
+ String separator;
+ };
+ };
+
+ Dictionary env {
+ String "*";
+ Function "*";
+ };
+};
+
+}
diff --git a/lib/icinga/comment.cpp b/lib/icinga/comment.cpp
new file mode 100644
index 0000000..9c0b923
--- /dev/null
+++ b/lib/icinga/comment.cpp
@@ -0,0 +1,258 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/comment.hpp"
+#include "icinga/comment-ti.cpp"
+#include "icinga/host.hpp"
+#include "remote/configobjectutility.hpp"
+#include "base/utility.hpp"
+#include "base/configtype.hpp"
+#include "base/timer.hpp"
+#include <boost/thread/once.hpp>
+
+using namespace icinga;
+
+static int l_NextCommentID = 1;
+static std::mutex l_CommentMutex;
+static std::map<int, String> l_LegacyCommentsCache;
+static Timer::Ptr l_CommentsExpireTimer;
+
+boost::signals2::signal<void (const Comment::Ptr&)> Comment::OnCommentAdded;
+boost::signals2::signal<void (const Comment::Ptr&)> Comment::OnCommentRemoved;
+boost::signals2::signal<void (const Comment::Ptr&, const String&, double, const MessageOrigin::Ptr&)> Comment::OnRemovalInfoChanged;
+
+REGISTER_TYPE(Comment);
+
+String CommentNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ Comment::Ptr comment = dynamic_pointer_cast<Comment>(context);
+
+ if (!comment)
+ return "";
+
+ String name = comment->GetHostName();
+
+ if (!comment->GetServiceName().IsEmpty())
+ name += "!" + comment->GetServiceName();
+
+ name += "!" + shortName;
+
+ return name;
+}
+
+Dictionary::Ptr CommentNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Comment name."));
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("host_name", tokens[0]);
+
+ if (tokens.size() > 2) {
+ result->Set("service_name", tokens[1]);
+ result->Set("name", tokens[2]);
+ } else {
+ result->Set("name", tokens[1]);
+ }
+
+ return result;
+}
+
+void Comment::OnAllConfigLoaded()
+{
+ ConfigObject::OnAllConfigLoaded();
+
+ Host::Ptr host = Host::GetByName(GetHostName());
+
+ if (GetServiceName().IsEmpty())
+ m_Checkable = host;
+ else
+ m_Checkable = host->GetServiceByShortName(GetServiceName());
+
+ if (!m_Checkable)
+ BOOST_THROW_EXCEPTION(ScriptError("Comment '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
+}
+
+void Comment::Start(bool runtimeCreated)
+{
+ ObjectImpl<Comment>::Start(runtimeCreated);
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, [this]() {
+ l_CommentsExpireTimer = Timer::Create();
+ l_CommentsExpireTimer->SetInterval(60);
+ l_CommentsExpireTimer->OnTimerExpired.connect([](const Timer * const&) { CommentsExpireTimerHandler(); });
+ l_CommentsExpireTimer->Start();
+ });
+
+ {
+ std::unique_lock<std::mutex> lock(l_CommentMutex);
+
+ SetLegacyId(l_NextCommentID);
+ l_LegacyCommentsCache[l_NextCommentID] = GetName();
+ l_NextCommentID++;
+ }
+
+ GetCheckable()->RegisterComment(this);
+
+ if (runtimeCreated)
+ OnCommentAdded(this);
+}
+
+void Comment::Stop(bool runtimeRemoved)
+{
+ GetCheckable()->UnregisterComment(this);
+
+ if (runtimeRemoved)
+ OnCommentRemoved(this);
+
+ ObjectImpl<Comment>::Stop(runtimeRemoved);
+}
+
+Checkable::Ptr Comment::GetCheckable() const
+{
+ return static_pointer_cast<Checkable>(m_Checkable);
+}
+
+bool Comment::IsExpired() const
+{
+ double expire_time = GetExpireTime();
+
+ return (expire_time != 0 && expire_time < Utility::GetTime());
+}
+
+int Comment::GetNextCommentID()
+{
+ std::unique_lock<std::mutex> lock(l_CommentMutex);
+
+ return l_NextCommentID;
+}
+
+String Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryType, const String& author,
+ const String& text, bool persistent, double expireTime, bool sticky, const String& id, const MessageOrigin::Ptr& origin)
+{
+ String fullName;
+
+ if (id.IsEmpty())
+ fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
+ else
+ fullName = id;
+
+ Dictionary::Ptr attrs = new Dictionary();
+
+ attrs->Set("author", author);
+ attrs->Set("text", text);
+ attrs->Set("persistent", persistent);
+ attrs->Set("expire_time", expireTime);
+ attrs->Set("entry_type", entryType);
+ attrs->Set("sticky", sticky);
+ attrs->Set("entry_time", Utility::GetTime());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ attrs->Set("host_name", host->GetName());
+ if (service)
+ attrs->Set("service_name", service->GetShortName());
+
+ String zone = checkable->GetZoneName();
+
+ if (!zone.IsEmpty())
+ attrs->Set("zone", zone);
+
+ String config = ConfigObjectUtility::CreateObjectConfig(Comment::TypeInstance, fullName, true, nullptr, attrs);
+
+ Array::Ptr errors = new Array();
+
+ if (!ConfigObjectUtility::CreateObject(Comment::TypeInstance, fullName, config, errors, nullptr)) {
+ ObjectLock olock(errors);
+ for (const String& error : errors) {
+ Log(LogCritical, "Comment", error);
+ }
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not create comment."));
+ }
+
+ Comment::Ptr comment = Comment::GetByName(fullName);
+
+ if (!comment)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not create comment."));
+
+ Log(LogNotice, "Comment")
+ << "Added comment '" << comment->GetName() << "'.";
+
+ return fullName;
+}
+
+void Comment::RemoveComment(const String& id, bool removedManually, const String& removedBy,
+ const MessageOrigin::Ptr& origin)
+{
+ Comment::Ptr comment = Comment::GetByName(id);
+
+ if (!comment || comment->GetPackage() != "_api")
+ return;
+
+ Log(LogNotice, "Comment")
+ << "Removed comment '" << comment->GetName() << "' from object '" << comment->GetCheckable()->GetName() << "'.";
+
+ if (removedManually) {
+ comment->SetRemovalInfo(removedBy, Utility::GetTime());
+ }
+
+ Array::Ptr errors = new Array();
+
+ if (!ConfigObjectUtility::DeleteObject(comment, false, errors, nullptr)) {
+ ObjectLock olock(errors);
+ for (const String& error : errors) {
+ Log(LogCritical, "Comment", error);
+ }
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove comment."));
+ }
+}
+
+void Comment::SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin) {
+ {
+ ObjectLock olock(this);
+
+ SetRemovedBy(removedBy, false, origin);
+ SetRemoveTime(removeTime, false, origin);
+ }
+
+ OnRemovalInfoChanged(this, removedBy, removeTime, origin);
+}
+
+String Comment::GetCommentIDFromLegacyID(int id)
+{
+ std::unique_lock<std::mutex> lock(l_CommentMutex);
+
+ auto it = l_LegacyCommentsCache.find(id);
+
+ if (it == l_LegacyCommentsCache.end())
+ return Empty;
+
+ return it->second;
+}
+
+void Comment::CommentsExpireTimerHandler()
+{
+ std::vector<Comment::Ptr> comments;
+
+ for (const Comment::Ptr& comment : ConfigType::GetObjectsByType<Comment>()) {
+ comments.push_back(comment);
+ }
+
+ for (const Comment::Ptr& comment : comments) {
+ /* Only remove comments which are activated after daemon start. */
+ if (comment->IsActive() && comment->IsExpired()) {
+ /* Do not remove persistent comments from an acknowledgement */
+ if (comment->GetEntryType() == CommentAcknowledgement && comment->GetPersistent())
+ continue;
+
+ RemoveComment(comment->GetName());
+ }
+ }
+}
diff --git a/lib/icinga/comment.hpp b/lib/icinga/comment.hpp
new file mode 100644
index 0000000..6532084
--- /dev/null
+++ b/lib/icinga/comment.hpp
@@ -0,0 +1,59 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COMMENT_H
+#define COMMENT_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/comment-ti.hpp"
+#include "icinga/checkable-ti.hpp"
+#include "remote/messageorigin.hpp"
+
+namespace icinga
+{
+
+/**
+ * A comment.
+ *
+ * @ingroup icinga
+ */
+class Comment final : public ObjectImpl<Comment>
+{
+public:
+ DECLARE_OBJECT(Comment);
+ DECLARE_OBJECTNAME(Comment);
+
+ static boost::signals2::signal<void (const Comment::Ptr&)> OnCommentAdded;
+ static boost::signals2::signal<void (const Comment::Ptr&)> OnCommentRemoved;
+ static boost::signals2::signal<void (const Comment::Ptr&, const String&, double, const MessageOrigin::Ptr&)> OnRemovalInfoChanged;
+
+ intrusive_ptr<Checkable> GetCheckable() const;
+
+ bool IsExpired() const;
+
+ void SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin = nullptr);
+
+ static int GetNextCommentID();
+
+ static String AddComment(const intrusive_ptr<Checkable>& checkable, CommentType entryType,
+ const String& author, const String& text, bool persistent, double expireTime, bool sticky = false,
+ const String& id = String(), const MessageOrigin::Ptr& origin = nullptr);
+
+ static void RemoveComment(const String& id, bool removedManually = false, const String& removedBy = "",
+ const MessageOrigin::Ptr& origin = nullptr);
+
+ static String GetCommentIDFromLegacyID(int id);
+
+protected:
+ void OnAllConfigLoaded() override;
+ void Start(bool runtimeCreated) override;
+ void Stop(bool runtimeRemoved) override;
+
+private:
+ ObjectImpl<Checkable>::Ptr m_Checkable;
+
+ static void CommentsExpireTimerHandler();
+};
+
+}
+
+#endif /* COMMENT_H */
diff --git a/lib/icinga/comment.ti b/lib/icinga/comment.ti
new file mode 100644
index 0000000..b8ad6f7
--- /dev/null
+++ b/lib/icinga/comment.ti
@@ -0,0 +1,80 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/configobject.hpp"
+#include "base/utility.hpp"
+#impl_include "icinga/service.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+/**
+ * The type of a service comment.
+ *
+ * @ingroup icinga
+ */
+enum CommentType
+{
+ CommentUser = 1,
+ CommentAcknowledgement = 4
+};
+
+class CommentNameComposer : public NameComposer
+{
+public:
+ virtual String MakeName(const String& shortName, const Object::Ptr& context) const;
+ virtual Dictionary::Ptr ParseName(const String& name) const;
+};
+}}}
+
+class Comment : ConfigObject < CommentNameComposer
+{
+ load_after Host;
+ load_after Service;
+
+ [config, no_user_modify, protected, required, navigation(host)] name(Host) host_name {
+ navigate {{{
+ return Host::GetByName(GetHostName());
+ }}}
+ };
+ [config, no_user_modify, protected, navigation(service)] String service_name {
+ track {{{
+ if (!oldValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), oldValue);
+ DependencyGraph::RemoveDependency(this, service.get());
+ }
+
+ if (!newValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), newValue);
+ DependencyGraph::AddDependency(this, service.get());
+ }
+ }}}
+ navigate {{{
+ if (GetServiceName().IsEmpty())
+ return nullptr;
+
+ Host::Ptr host = Host::GetByName(GetHostName());
+ return host->GetServiceByShortName(GetServiceName());
+ }}}
+ };
+
+ [config] Timestamp entry_time {
+ default {{{ return Utility::GetTime(); }}}
+ };
+ [config, enum] CommentType entry_type {
+ default {{{ return CommentUser; }}}
+ };
+ [config, no_user_view, no_user_modify] bool sticky;
+ [config, required] String author;
+ [config, required] String text;
+ [config] bool persistent;
+ [config] Timestamp expire_time;
+ [state] int legacy_id;
+
+ [no_user_view, no_user_modify] String removed_by;
+ [no_user_view, no_user_modify] Timestamp remove_time;
+};
+
+}
diff --git a/lib/icinga/compatutility.cpp b/lib/icinga/compatutility.cpp
new file mode 100644
index 0000000..95aed43
--- /dev/null
+++ b/lib/icinga/compatutility.cpp
@@ -0,0 +1,302 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/compatutility.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/pluginutility.hpp"
+#include "icinga/service.hpp"
+#include "base/utility.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+using namespace icinga;
+
+/* Used in DB IDO and Livestatus. */
+String CompatUtility::GetCommandLine(const Command::Ptr& command)
+{
+ Value commandLine = command->GetCommandLine();
+
+ String result;
+ if (commandLine.IsObjectType<Array>()) {
+ Array::Ptr args = commandLine;
+
+ ObjectLock olock(args);
+ for (const String& arg : args) {
+ // This is obviously incorrect for non-trivial cases.
+ result += " \"" + EscapeString(arg) + "\"";
+ }
+ } else if (!commandLine.IsEmpty()) {
+ result = EscapeString(Convert::ToString(commandLine));
+ } else {
+ result = "<internal>";
+ }
+
+ return result;
+}
+
+String CompatUtility::GetCommandNamePrefix(const Command::Ptr& command)
+/* Helper. */
+{
+ if (!command)
+ return Empty;
+
+ String prefix;
+ if (command->GetReflectionType() == CheckCommand::TypeInstance)
+ prefix = "check_";
+ else if (command->GetReflectionType() == NotificationCommand::TypeInstance)
+ prefix = "notification_";
+ else if (command->GetReflectionType() == EventCommand::TypeInstance)
+ prefix = "event_";
+
+ return prefix;
+}
+
+String CompatUtility::GetCommandName(const Command::Ptr& command)
+/* Used in DB IDO and Livestatus. */
+{
+ if (!command)
+ return Empty;
+
+ return GetCommandNamePrefix(command) + command->GetName();
+}
+
+/* Used in DB IDO and Livestatus. */
+String CompatUtility::GetCheckableCommandArgs(const Checkable::Ptr& checkable)
+{
+ CheckCommand::Ptr command = checkable->GetCheckCommand();
+
+ Dictionary::Ptr args = new Dictionary();
+
+ if (command) {
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+ String command_line = GetCommandLine(command);
+
+ Dictionary::Ptr command_vars = command->GetVars();
+
+ if (command_vars) {
+ ObjectLock olock(command_vars);
+ for (const Dictionary::Pair& kv : command_vars) {
+ String macro = "$" + kv.first + "$"; // this is too simple
+ if (command_line.Contains(macro))
+ args->Set(kv.first, kv.second);
+
+ }
+ }
+
+ Dictionary::Ptr host_vars = host->GetVars();
+
+ if (host_vars) {
+ ObjectLock olock(host_vars);
+ for (const Dictionary::Pair& kv : host_vars) {
+ String macro = "$" + kv.first + "$"; // this is too simple
+ if (command_line.Contains(macro))
+ args->Set(kv.first, kv.second);
+ macro = "$host.vars." + kv.first + "$";
+ if (command_line.Contains(macro))
+ args->Set(kv.first, kv.second);
+ }
+ }
+
+ if (service) {
+ Dictionary::Ptr service_vars = service->GetVars();
+
+ if (service_vars) {
+ ObjectLock olock(service_vars);
+ for (const Dictionary::Pair& kv : service_vars) {
+ String macro = "$" + kv.first + "$"; // this is too simple
+ if (command_line.Contains(macro))
+ args->Set(kv.first, kv.second);
+ macro = "$service.vars." + kv.first + "$";
+ if (command_line.Contains(macro))
+ args->Set(kv.first, kv.second);
+ }
+ }
+ }
+
+ String arg_string;
+ ObjectLock olock(args);
+ for (const Dictionary::Pair& kv : args) {
+ arg_string += Convert::ToString(kv.first) + "=" + Convert::ToString(kv.second) + "!";
+ }
+ return arg_string;
+ }
+
+ return Empty;
+}
+
+/* Used in DB IDO and Livestatus. */
+int CompatUtility::GetCheckableNotificationLastNotification(const Checkable::Ptr& checkable)
+{
+ double last_notification = 0.0;
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ if (notification->GetLastNotification() > last_notification)
+ last_notification = notification->GetLastNotification();
+ }
+
+ return static_cast<int>(last_notification);
+}
+
+/* Used in DB IDO and Livestatus. */
+int CompatUtility::GetCheckableNotificationNextNotification(const Checkable::Ptr& checkable)
+{
+ double next_notification = 0.0;
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ if (next_notification == 0 || notification->GetNextNotification() < next_notification)
+ next_notification = notification->GetNextNotification();
+ }
+
+ return static_cast<int>(next_notification);
+}
+
+/* Used in DB IDO and Livestatus. */
+int CompatUtility::GetCheckableNotificationNotificationNumber(const Checkable::Ptr& checkable)
+{
+ int notification_number = 0;
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ if (notification->GetNotificationNumber() > notification_number)
+ notification_number = notification->GetNotificationNumber();
+ }
+
+ return notification_number;
+}
+
+/* Used in DB IDO and Livestatus. */
+double CompatUtility::GetCheckableNotificationNotificationInterval(const Checkable::Ptr& checkable)
+{
+ double notification_interval = -1;
+
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ if (notification_interval == -1 || notification->GetInterval() < notification_interval)
+ notification_interval = notification->GetInterval();
+ }
+
+ if (notification_interval == -1)
+ notification_interval = 60;
+
+ return notification_interval / 60.0;
+}
+
+/* Helper. */
+int CompatUtility::GetCheckableNotificationTypeFilter(const Checkable::Ptr& checkable)
+{
+ unsigned long notification_type_filter = 0;
+
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ ObjectLock olock(notification);
+
+ notification_type_filter |= notification->GetTypeFilter();
+ }
+
+ return notification_type_filter;
+}
+
+/* Helper. */
+int CompatUtility::GetCheckableNotificationStateFilter(const Checkable::Ptr& checkable)
+{
+ unsigned long notification_state_filter = 0;
+
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ ObjectLock olock(notification);
+
+ notification_state_filter |= notification->GetStateFilter();
+ }
+
+ return notification_state_filter;
+}
+
+/* Used in DB IDO and Livestatus. */
+std::set<User::Ptr> CompatUtility::GetCheckableNotificationUsers(const Checkable::Ptr& checkable)
+{
+ /* Service -> Notifications -> (Users + UserGroups -> Users) */
+ std::set<User::Ptr> allUsers;
+ std::set<User::Ptr> users;
+
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ ObjectLock olock(notification);
+
+ users = notification->GetUsers();
+
+ std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
+
+ for (const UserGroup::Ptr& ug : notification->GetUserGroups()) {
+ std::set<User::Ptr> members = ug->GetMembers();
+ std::copy(members.begin(), members.end(), std::inserter(allUsers, allUsers.begin()));
+ }
+ }
+
+ return allUsers;
+}
+
+/* Used in DB IDO and Livestatus. */
+std::set<UserGroup::Ptr> CompatUtility::GetCheckableNotificationUserGroups(const Checkable::Ptr& checkable)
+{
+ std::set<UserGroup::Ptr> usergroups;
+ /* Service -> Notifications -> UserGroups */
+ for (const Notification::Ptr& notification : checkable->GetNotifications()) {
+ ObjectLock olock(notification);
+
+ for (const UserGroup::Ptr& ug : notification->GetUserGroups()) {
+ usergroups.insert(ug);
+ }
+ }
+
+ return usergroups;
+}
+
+/* Used in DB IDO, Livestatus, CompatLogger, GelfWriter, IcingaDB. */
+String CompatUtility::GetCheckResultOutput(const CheckResult::Ptr& cr)
+{
+ if (!cr)
+ return Empty;
+
+ String output;
+
+ String raw_output = cr->GetOutput();
+
+ size_t line_end = raw_output.Find("\n");
+
+ return raw_output.SubStr(0, line_end);
+}
+
+/* Used in DB IDO, Livestatus and IcingaDB. */
+String CompatUtility::GetCheckResultLongOutput(const CheckResult::Ptr& cr)
+{
+ if (!cr)
+ return Empty;
+
+ String long_output;
+ String output;
+
+ String raw_output = cr->GetOutput();
+
+ size_t line_end = raw_output.Find("\n");
+
+ if (line_end > 0 && line_end != String::NPos) {
+ long_output = raw_output.SubStr(line_end+1, raw_output.GetLength());
+ return EscapeString(long_output);
+ }
+
+ return Empty;
+}
+
+/* Helper for DB IDO and Livestatus. */
+String CompatUtility::EscapeString(const String& str)
+{
+ String result = str;
+ boost::algorithm::replace_all(result, "\n", "\\n");
+ return result;
+}
+
+/* Used in ExternalCommandListener. */
+String CompatUtility::UnEscapeString(const String& str)
+{
+ String result = str;
+ boost::algorithm::replace_all(result, "\\n", "\n");
+ return result;
+}
diff --git a/lib/icinga/compatutility.hpp b/lib/icinga/compatutility.hpp
new file mode 100644
index 0000000..7b96fb3
--- /dev/null
+++ b/lib/icinga/compatutility.hpp
@@ -0,0 +1,56 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef COMPATUTILITY_H
+#define COMPATUTILITY_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/host.hpp"
+#include "icinga/command.hpp"
+
+namespace icinga
+{
+
+/**
+ * Compatibility utility functions.
+ *
+ * @ingroup icinga
+ */
+class CompatUtility
+{
+public:
+ /* command */
+ static String GetCommandLine(const Command::Ptr& command);
+ static String GetCommandName(const Command::Ptr& command);
+
+ /* service */
+ static String GetCheckableCommandArgs(const Checkable::Ptr& checkable);
+
+ /* notification */
+ static int GetCheckableNotificationsEnabled(const Checkable::Ptr& checkable);
+ static int GetCheckableNotificationLastNotification(const Checkable::Ptr& checkable);
+ static int GetCheckableNotificationNextNotification(const Checkable::Ptr& checkable);
+ static int GetCheckableNotificationNotificationNumber(const Checkable::Ptr& checkable);
+ static double GetCheckableNotificationNotificationInterval(const Checkable::Ptr& checkable);
+ static int GetCheckableNotificationTypeFilter(const Checkable::Ptr& checkable);
+ static int GetCheckableNotificationStateFilter(const Checkable::Ptr& checkable);
+
+ static std::set<User::Ptr> GetCheckableNotificationUsers(const Checkable::Ptr& checkable);
+ static std::set<UserGroup::Ptr> GetCheckableNotificationUserGroups(const Checkable::Ptr& checkable);
+
+ /* check result */
+ static String GetCheckResultOutput(const CheckResult::Ptr& cr);
+ static String GetCheckResultLongOutput(const CheckResult::Ptr& cr);
+
+ /* misc */
+ static String EscapeString(const String& str);
+ static String UnEscapeString(const String& str);
+
+private:
+ CompatUtility();
+
+ static String GetCommandNamePrefix(const Command::Ptr& command);
+};
+
+}
+
+#endif /* COMPATUTILITY_H */
diff --git a/lib/icinga/customvarobject.cpp b/lib/icinga/customvarobject.cpp
new file mode 100644
index 0000000..fc1fd27
--- /dev/null
+++ b/lib/icinga/customvarobject.cpp
@@ -0,0 +1,49 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#include "icinga/customvarobject-ti.cpp"
+#include "icinga/macroprocessor.hpp"
+#include "base/logger.hpp"
+#include "base/function.hpp"
+#include "base/exception.hpp"
+#include "base/objectlock.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(CustomVarObject);
+
+void CustomVarObject::ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ MacroProcessor::ValidateCustomVars(this, lvalue());
+}
+
+int icinga::FilterArrayToInt(const Array::Ptr& typeFilters, const std::map<String, int>& filterMap, int defaultValue)
+{
+ int resultTypeFilter;
+
+ if (!typeFilters)
+ return defaultValue;
+
+ resultTypeFilter = 0;
+
+ ObjectLock olock(typeFilters);
+ for (const Value& typeFilter : typeFilters) {
+ if (typeFilter.IsNumber()) {
+ resultTypeFilter = resultTypeFilter | typeFilter;
+ continue;
+ }
+
+ if (!typeFilter.IsString())
+ return -1;
+
+ auto it = filterMap.find(typeFilter);
+
+ if (it == filterMap.end())
+ return -1;
+
+ resultTypeFilter = resultTypeFilter | it->second;
+ }
+
+ return resultTypeFilter;
+}
+
diff --git a/lib/icinga/customvarobject.hpp b/lib/icinga/customvarobject.hpp
new file mode 100644
index 0000000..e10ef32
--- /dev/null
+++ b/lib/icinga/customvarobject.hpp
@@ -0,0 +1,31 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef CUSTOMVAROBJECT_H
+#define CUSTOMVAROBJECT_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/customvarobject-ti.hpp"
+#include "base/configobject.hpp"
+#include "remote/messageorigin.hpp"
+
+namespace icinga
+{
+
+/**
+ * An object with custom variable attribute.
+ *
+ * @ingroup icinga
+ */
+class CustomVarObject : public ObjectImpl<CustomVarObject>
+{
+public:
+ DECLARE_OBJECT(CustomVarObject);
+
+ void ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) final;
+};
+
+int FilterArrayToInt(const Array::Ptr& typeFilters, const std::map<String, int>& filterMap, int defaultValue);
+
+}
+
+#endif /* CUSTOMVAROBJECT_H */
diff --git a/lib/icinga/customvarobject.ti b/lib/icinga/customvarobject.ti
new file mode 100644
index 0000000..3e40f66
--- /dev/null
+++ b/lib/icinga/customvarobject.ti
@@ -0,0 +1,15 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/configobject.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+abstract class CustomVarObject : ConfigObject
+{
+ [config, signal_with_old_value] Dictionary::Ptr vars;
+};
+
+}
diff --git a/lib/icinga/dependency-apply.cpp b/lib/icinga/dependency-apply.cpp
new file mode 100644
index 0000000..8681c43
--- /dev/null
+++ b/lib/icinga/dependency-apply.cpp
@@ -0,0 +1,161 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/dependency.hpp"
+#include "icinga/service.hpp"
+#include "config/configitembuilder.hpp"
+#include "config/applyrule.hpp"
+#include "base/initialize.hpp"
+#include "base/configtype.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/workqueue.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+INITIALIZE_ONCE([]() {
+ ApplyRule::RegisterType("Dependency", { "Host", "Service" });
+});
+
+bool Dependency::EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter)
+{
+ if (!skipFilter && !rule.EvaluateFilter(frame))
+ return false;
+
+ auto& di (rule.GetDebugInfo());
+
+#ifdef _DEBUG
+ Log(LogDebug, "Dependency")
+ << "Applying dependency '" << name << "' to object '" << checkable->GetName() << "' for rule " << di;
+#endif /* _DEBUG */
+
+ ConfigItemBuilder builder{di};
+ builder.SetType(Dependency::TypeInstance);
+ builder.SetName(name);
+ builder.SetScope(frame.Locals->ShallowClone());
+ builder.SetIgnoreOnError(rule.GetIgnoreOnError());
+
+ builder.AddExpression(new ImportDefaultTemplatesExpression());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "parent_host_name"), OpSetLiteral, MakeLiteral(host->GetName()), di));
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "child_host_name"), OpSetLiteral, MakeLiteral(host->GetName()), di));
+
+ if (service)
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "child_service_name"), OpSetLiteral, MakeLiteral(service->GetShortName()), di));
+
+ String zone = checkable->GetZoneName();
+
+ if (!zone.IsEmpty())
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "zone"), OpSetLiteral, MakeLiteral(zone), di));
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "package"), OpSetLiteral, MakeLiteral(rule.GetPackage()), di));
+
+ builder.AddExpression(new OwnedExpression(rule.GetExpression()));
+
+ ConfigItem::Ptr dependencyItem = builder.Compile();
+ dependencyItem->Register();
+
+ return true;
+}
+
+bool Dependency::EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter)
+{
+ auto& di (rule.GetDebugInfo());
+
+ CONTEXT("Evaluating 'apply' rule (" << di << ")");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ ScriptFrame frame(true);
+ if (rule.GetScope())
+ rule.GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("host", host);
+ if (service)
+ frame.Locals->Set("service", service);
+
+ Value vinstances;
+
+ if (rule.GetFTerm()) {
+ try {
+ vinstances = rule.GetFTerm()->Evaluate(frame);
+ } catch (const std::exception&) {
+ /* Silently ignore errors here and assume there are no instances. */
+ return false;
+ }
+ } else {
+ vinstances = new Array({ "" });
+ }
+
+ bool match = false;
+
+ if (vinstances.IsObjectType<Array>()) {
+ if (!rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di));
+
+ Array::Ptr arr = vinstances;
+
+ ObjectLock olock(arr);
+ for (const Value& instance : arr) {
+ String name = rule.GetName();
+
+ if (!rule.GetFKVar().IsEmpty()) {
+ frame.Locals->Set(rule.GetFKVar(), instance);
+ name += instance;
+ }
+
+ if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter))
+ match = true;
+ }
+ } else if (vinstances.IsObjectType<Dictionary>()) {
+ if (rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di));
+
+ Dictionary::Ptr dict = vinstances;
+
+ for (const String& key : dict->GetKeys()) {
+ frame.Locals->Set(rule.GetFKVar(), key);
+ frame.Locals->Set(rule.GetFVVar(), dict->Get(key));
+
+ if (EvaluateApplyRuleInstance(checkable, rule.GetName() + key, frame, rule, skipFilter))
+ match = true;
+ }
+ }
+
+ return match;
+}
+
+void Dependency::EvaluateApplyRules(const Host::Ptr& host)
+{
+ CONTEXT("Evaluating 'apply' rules for host '" << host->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(Dependency::TypeInstance, Host::TypeInstance)) {
+ if (EvaluateApplyRule(host, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedHostRules(Dependency::TypeInstance, host->GetName())) {
+ if (EvaluateApplyRule(host, *rule, true))
+ rule->AddMatch();
+ }
+}
+
+void Dependency::EvaluateApplyRules(const Service::Ptr& service)
+{
+ CONTEXT("Evaluating 'apply' rules for service '" << service->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(Dependency::TypeInstance, Service::TypeInstance)) {
+ if (EvaluateApplyRule(service, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedServiceRules(Dependency::TypeInstance, service->GetHost()->GetName(), service->GetShortName())) {
+ if (EvaluateApplyRule(service, *rule, true))
+ rule->AddMatch();
+ }
+}
diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp
new file mode 100644
index 0000000..2843b90
--- /dev/null
+++ b/lib/icinga/dependency.cpp
@@ -0,0 +1,325 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/dependency.hpp"
+#include "icinga/dependency-ti.cpp"
+#include "icinga/service.hpp"
+#include "base/configobject.hpp"
+#include "base/initialize.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include <map>
+#include <sstream>
+#include <utility>
+
+using namespace icinga;
+
+REGISTER_TYPE(Dependency);
+
+bool Dependency::m_AssertNoCyclesForIndividualDeps = false;
+
+struct DependencyCycleNode
+{
+ bool Visited = false;
+ bool OnStack = false;
+};
+
+struct DependencyStackFrame
+{
+ ConfigObject::Ptr Node;
+ bool Implicit;
+
+ inline DependencyStackFrame(ConfigObject::Ptr node, bool implicit = false) : Node(std::move(node)), Implicit(implicit)
+ { }
+};
+
+struct DependencyCycleGraph
+{
+ std::map<Checkable::Ptr, DependencyCycleNode> Nodes;
+ std::vector<DependencyStackFrame> Stack;
+};
+
+static void AssertNoDependencyCycle(const Checkable::Ptr& checkable, DependencyCycleGraph& graph, bool implicit = false);
+
+static void AssertNoParentDependencyCycle(const Checkable::Ptr& parent, DependencyCycleGraph& graph, bool implicit)
+{
+ if (graph.Nodes[parent].OnStack) {
+ std::ostringstream oss;
+ oss << "Dependency cycle:\n";
+
+ for (auto& frame : graph.Stack) {
+ oss << frame.Node->GetReflectionType()->GetName() << " '" << frame.Node->GetName() << "'";
+
+ if (frame.Implicit) {
+ oss << " (implicit)";
+ }
+
+ oss << "\n-> ";
+ }
+
+ oss << parent->GetReflectionType()->GetName() << " '" << parent->GetName() << "'";
+
+ if (implicit) {
+ oss << " (implicit)";
+ }
+
+ BOOST_THROW_EXCEPTION(ScriptError(oss.str()));
+ }
+
+ AssertNoDependencyCycle(parent, graph, implicit);
+}
+
+static void AssertNoDependencyCycle(const Checkable::Ptr& checkable, DependencyCycleGraph& graph, bool implicit)
+{
+ auto& node (graph.Nodes[checkable]);
+
+ if (!node.Visited) {
+ node.Visited = true;
+ node.OnStack = true;
+ graph.Stack.emplace_back(checkable, implicit);
+
+ for (auto& dep : checkable->GetDependencies()) {
+ graph.Stack.emplace_back(dep);
+ AssertNoParentDependencyCycle(dep->GetParent(), graph, false);
+ graph.Stack.pop_back();
+ }
+
+ {
+ auto service (dynamic_pointer_cast<Service>(checkable));
+
+ if (service) {
+ AssertNoParentDependencyCycle(service->GetHost(), graph, true);
+ }
+ }
+
+ graph.Stack.pop_back();
+ node.OnStack = false;
+ }
+}
+
+void Dependency::AssertNoCycles()
+{
+ DependencyCycleGraph graph;
+
+ for (auto& host : ConfigType::GetObjectsByType<Host>()) {
+ AssertNoDependencyCycle(host, graph);
+ }
+
+ for (auto& service : ConfigType::GetObjectsByType<Service>()) {
+ AssertNoDependencyCycle(service, graph);
+ }
+
+ m_AssertNoCyclesForIndividualDeps = true;
+}
+
+String DependencyNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ Dependency::Ptr dependency = dynamic_pointer_cast<Dependency>(context);
+
+ if (!dependency)
+ return "";
+
+ String name = dependency->GetChildHostName();
+
+ if (!dependency->GetChildServiceName().IsEmpty())
+ name += "!" + dependency->GetChildServiceName();
+
+ name += "!" + shortName;
+
+ return name;
+}
+
+Dictionary::Ptr DependencyNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Dependency name."));
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("child_host_name", tokens[0]);
+
+ if (tokens.size() > 2) {
+ result->Set("child_service_name", tokens[1]);
+ result->Set("name", tokens[2]);
+ } else {
+ result->Set("name", tokens[1]);
+ }
+
+ return result;
+}
+
+void Dependency::OnConfigLoaded()
+{
+ Value defaultFilter;
+
+ if (GetParentServiceName().IsEmpty())
+ defaultFilter = StateFilterUp;
+ else
+ defaultFilter = StateFilterOK | StateFilterWarning;
+
+ SetStateFilter(FilterArrayToInt(GetStates(), Notification::GetStateFilterMap(), defaultFilter));
+}
+
+void Dependency::OnAllConfigLoaded()
+{
+ ObjectImpl<Dependency>::OnAllConfigLoaded();
+
+ Host::Ptr childHost = Host::GetByName(GetChildHostName());
+
+ if (childHost) {
+ if (GetChildServiceName().IsEmpty())
+ m_Child = childHost;
+ else
+ m_Child = childHost->GetServiceByShortName(GetChildServiceName());
+ }
+
+ if (!m_Child)
+ BOOST_THROW_EXCEPTION(ScriptError("Dependency '" + GetName() + "' references a child host/service which doesn't exist.", GetDebugInfo()));
+
+ Host::Ptr parentHost = Host::GetByName(GetParentHostName());
+
+ if (parentHost) {
+ if (GetParentServiceName().IsEmpty())
+ m_Parent = parentHost;
+ else
+ m_Parent = parentHost->GetServiceByShortName(GetParentServiceName());
+ }
+
+ if (!m_Parent)
+ BOOST_THROW_EXCEPTION(ScriptError("Dependency '" + GetName() + "' references a parent host/service which doesn't exist.", GetDebugInfo()));
+
+ m_Child->AddDependency(this);
+ m_Parent->AddReverseDependency(this);
+
+ if (m_AssertNoCyclesForIndividualDeps) {
+ DependencyCycleGraph graph;
+
+ try {
+ AssertNoDependencyCycle(m_Parent, graph);
+ } catch (...) {
+ m_Child->RemoveDependency(this);
+ m_Parent->RemoveReverseDependency(this);
+ throw;
+ }
+ }
+}
+
+void Dependency::Stop(bool runtimeRemoved)
+{
+ ObjectImpl<Dependency>::Stop(runtimeRemoved);
+
+ GetChild()->RemoveDependency(this);
+ GetParent()->RemoveReverseDependency(this);
+}
+
+bool Dependency::IsAvailable(DependencyType dt) const
+{
+ Checkable::Ptr parent = GetParent();
+
+ Host::Ptr parentHost;
+ Service::Ptr parentService;
+ tie(parentHost, parentService) = GetHostService(parent);
+
+ /* ignore if it's the same checkable object */
+ if (parent == GetChild()) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Parent and child " << (parentService ? "service" : "host") << " are identical.";
+ return true;
+ }
+
+ /* ignore pending */
+ if (!parent->GetLastCheckResult()) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Parent " << (parentService ? "service" : "host") << " '" << parent->GetName() << "' hasn't been checked yet.";
+ return true;
+ }
+
+ if (GetIgnoreSoftStates()) {
+ /* ignore soft states */
+ if (parent->GetStateType() == StateTypeSoft) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Parent " << (parentService ? "service" : "host") << " '" << parent->GetName() << "' is in a soft state.";
+ return true;
+ }
+ } else {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' failed: Parent " << (parentService ? "service" : "host") << " '" << parent->GetName() << "' is in a soft state.";
+ }
+
+ int state;
+
+ if (parentService)
+ state = ServiceStateToFilter(parentService->GetState());
+ else
+ state = HostStateToFilter(parentHost->GetState());
+
+ /* check state */
+ if (state & GetStateFilter()) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Parent " << (parentService ? "service" : "host") << " '" << parent->GetName() << "' matches state filter.";
+ return true;
+ }
+
+ /* ignore if not in time period */
+ TimePeriod::Ptr tp = GetPeriod();
+ if (tp && !tp->IsInside(Utility::GetTime())) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Outside time period.";
+ return true;
+ }
+
+ if (dt == DependencyCheckExecution && !GetDisableChecks()) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Checks are not disabled.";
+ return true;
+ } else if (dt == DependencyNotification && !GetDisableNotifications()) {
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' passed: Notifications are not disabled";
+ return true;
+ }
+
+ Log(LogNotice, "Dependency")
+ << "Dependency '" << GetName() << "' failed. Parent "
+ << (parentService ? "service" : "host") << " '" << parent->GetName() << "' is "
+ << (parentService ? Service::StateToString(parentService->GetState()) : Host::StateToString(parentHost->GetState()));
+
+ return false;
+}
+
+Checkable::Ptr Dependency::GetChild() const
+{
+ return m_Child;
+}
+
+Checkable::Ptr Dependency::GetParent() const
+{
+ return m_Parent;
+}
+
+TimePeriod::Ptr Dependency::GetPeriod() const
+{
+ return TimePeriod::GetByName(GetPeriodRaw());
+}
+
+void Dependency::ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Dependency>::ValidateStates(lvalue, utils);
+
+ int sfilter = FilterArrayToInt(lvalue(), Notification::GetStateFilterMap(), 0);
+
+ if (GetParentServiceName().IsEmpty() && (sfilter & ~(StateFilterUp | StateFilterDown)) != 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid for host dependency."));
+
+ if (!GetParentServiceName().IsEmpty() && (sfilter & ~(StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid for service dependency."));
+}
+
+void Dependency::SetParent(intrusive_ptr<Checkable> parent)
+{
+ m_Parent = parent;
+}
+
+void Dependency::SetChild(intrusive_ptr<Checkable> child)
+{
+ m_Child = child;
+}
diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp
new file mode 100644
index 0000000..6cebfaa
--- /dev/null
+++ b/lib/icinga/dependency.hpp
@@ -0,0 +1,62 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef DEPENDENCY_H
+#define DEPENDENCY_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/dependency-ti.hpp"
+
+namespace icinga
+{
+
+class ApplyRule;
+struct ScriptFrame;
+class Host;
+class Service;
+
+/**
+ * A service dependency..
+ *
+ * @ingroup icinga
+ */
+class Dependency final : public ObjectImpl<Dependency>
+{
+public:
+ DECLARE_OBJECT(Dependency);
+ DECLARE_OBJECTNAME(Dependency);
+
+ intrusive_ptr<Checkable> GetParent() const;
+ intrusive_ptr<Checkable> GetChild() const;
+
+ TimePeriod::Ptr GetPeriod() const;
+
+ bool IsAvailable(DependencyType dt) const;
+
+ void ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
+
+ static void EvaluateApplyRules(const intrusive_ptr<Host>& host);
+ static void EvaluateApplyRules(const intrusive_ptr<Service>& service);
+ static void AssertNoCycles();
+
+ /* Note: Only use them for unit test mocks. Prefer OnConfigLoaded(). */
+ void SetParent(intrusive_ptr<Checkable> parent);
+ void SetChild(intrusive_ptr<Checkable> child);
+
+protected:
+ void OnConfigLoaded() override;
+ void OnAllConfigLoaded() override;
+ void Stop(bool runtimeRemoved) override;
+
+private:
+ Checkable::Ptr m_Parent;
+ Checkable::Ptr m_Child;
+
+ static bool m_AssertNoCyclesForIndividualDeps;
+
+ static bool EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter);
+ static bool EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter = false);
+};
+
+}
+
+#endif /* DEPENDENCY_H */
diff --git a/lib/icinga/dependency.ti b/lib/icinga/dependency.ti
new file mode 100644
index 0000000..41de7ba
--- /dev/null
+++ b/lib/icinga/dependency.ti
@@ -0,0 +1,101 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#include "icinga/checkable.hpp"
+#impl_include "icinga/service.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+class DependencyNameComposer : public NameComposer
+{
+public:
+ virtual String MakeName(const String& shortName, const Object::Ptr& context) const;
+ virtual Dictionary::Ptr ParseName(const String& name) const;
+};
+}}}
+
+class Dependency : CustomVarObject < DependencyNameComposer
+{
+ load_after Host;
+ load_after Service;
+
+ [config, no_user_modify, required, navigation(child_host)] name(Host) child_host_name {
+ navigate {{{
+ return Host::GetByName(GetChildHostName());
+ }}}
+ };
+
+ [config, no_user_modify, navigation(child_service)] String child_service_name {
+ track {{{
+ if (!oldValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetChildHostName(), oldValue);
+ DependencyGraph::RemoveDependency(this, service.get());
+ }
+
+ if (!newValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetChildHostName(), newValue);
+ DependencyGraph::AddDependency(this, service.get());
+ }
+ }}}
+ navigate {{{
+ if (GetChildServiceName().IsEmpty())
+ return nullptr;
+
+ Host::Ptr host = Host::GetByName(GetChildHostName());
+ return host->GetServiceByShortName(GetChildServiceName());
+ }}}
+ };
+
+ [config, no_user_modify, required, navigation(parent_host)] name(Host) parent_host_name {
+ navigate {{{
+ return Host::GetByName(GetParentHostName());
+ }}}
+ };
+
+ [config, no_user_modify, navigation(parent_service)] String parent_service_name {
+ track {{{
+ if (!oldValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetParentHostName(), oldValue);
+ DependencyGraph::RemoveDependency(this, service.get());
+ }
+
+ if (!newValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetParentHostName(), newValue);
+ DependencyGraph::AddDependency(this, service.get());
+ }
+ }}}
+ navigate {{{
+ if (GetParentServiceName().IsEmpty())
+ return nullptr;
+
+ Host::Ptr host = Host::GetByName(GetParentHostName());
+ return host->GetServiceByShortName(GetParentServiceName());
+ }}}
+ };
+
+ [config] String redundancy_group;
+
+ [config, navigation] name(TimePeriod) period (PeriodRaw) {
+ navigate {{{
+ return TimePeriod::GetByName(GetPeriodRaw());
+ }}}
+ };
+
+ [config] array(Value) states;
+ [no_user_view, no_user_modify] int state_filter_real (StateFilter);
+
+ [config] bool ignore_soft_states {
+ default {{{ return true; }}}
+ };
+
+ [config] bool disable_checks;
+ [config] bool disable_notifications {
+ default {{{ return true; }}}
+ };
+};
+
+}
diff --git a/lib/icinga/downtime.cpp b/lib/icinga/downtime.cpp
new file mode 100644
index 0000000..2178953
--- /dev/null
+++ b/lib/icinga/downtime.cpp
@@ -0,0 +1,584 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/downtime.hpp"
+#include "icinga/downtime-ti.cpp"
+#include "icinga/host.hpp"
+#include "icinga/scheduleddowntime.hpp"
+#include "remote/configobjectutility.hpp"
+#include "base/configtype.hpp"
+#include "base/utility.hpp"
+#include "base/timer.hpp"
+#include <boost/thread/once.hpp>
+#include <cmath>
+#include <utility>
+
+using namespace icinga;
+
+static int l_NextDowntimeID = 1;
+static std::mutex l_DowntimeMutex;
+static std::map<int, String> l_LegacyDowntimesCache;
+static Timer::Ptr l_DowntimesOrphanedTimer;
+static Timer::Ptr l_DowntimesStartTimer;
+
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeAdded;
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeRemoved;
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeStarted;
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeTriggered;
+boost::signals2::signal<void (const Downtime::Ptr&, const String&, double, const MessageOrigin::Ptr&)> Downtime::OnRemovalInfoChanged;
+
+REGISTER_TYPE(Downtime);
+
+INITIALIZE_ONCE(&Downtime::StaticInitialize);
+
+void Downtime::StaticInitialize()
+{
+ ScriptGlobal::Set("Icinga.DowntimeNoChildren", "DowntimeNoChildren");
+ ScriptGlobal::Set("Icinga.DowntimeTriggeredChildren", "DowntimeTriggeredChildren");
+ ScriptGlobal::Set("Icinga.DowntimeNonTriggeredChildren", "DowntimeNonTriggeredChildren");
+}
+
+String DowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ Downtime::Ptr downtime = dynamic_pointer_cast<Downtime>(context);
+
+ if (!downtime)
+ return "";
+
+ String name = downtime->GetHostName();
+
+ if (!downtime->GetServiceName().IsEmpty())
+ name += "!" + downtime->GetServiceName();
+
+ name += "!" + shortName;
+
+ return name;
+}
+
+Dictionary::Ptr DowntimeNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Downtime name."));
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("host_name", tokens[0]);
+
+ if (tokens.size() > 2) {
+ result->Set("service_name", tokens[1]);
+ result->Set("name", tokens[2]);
+ } else {
+ result->Set("name", tokens[1]);
+ }
+
+ return result;
+}
+
+void Downtime::OnAllConfigLoaded()
+{
+ ObjectImpl<Downtime>::OnAllConfigLoaded();
+
+ if (GetServiceName().IsEmpty())
+ m_Checkable = Host::GetByName(GetHostName());
+ else
+ m_Checkable = Service::GetByNamePair(GetHostName(), GetServiceName());
+
+ if (!m_Checkable)
+ BOOST_THROW_EXCEPTION(ScriptError("Downtime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
+}
+
+void Downtime::Start(bool runtimeCreated)
+{
+ ObjectImpl<Downtime>::Start(runtimeCreated);
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, [this]() {
+ l_DowntimesStartTimer = Timer::Create();
+ l_DowntimesStartTimer->SetInterval(5);
+ l_DowntimesStartTimer->OnTimerExpired.connect([](const Timer * const&){ DowntimesStartTimerHandler(); });
+ l_DowntimesStartTimer->Start();
+
+ l_DowntimesOrphanedTimer = Timer::Create();
+ l_DowntimesOrphanedTimer->SetInterval(60);
+ l_DowntimesOrphanedTimer->OnTimerExpired.connect([](const Timer * const&) { DowntimesOrphanedTimerHandler(); });
+ l_DowntimesOrphanedTimer->Start();
+ });
+
+ {
+ std::unique_lock<std::mutex> lock(l_DowntimeMutex);
+
+ SetLegacyId(l_NextDowntimeID);
+ l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
+ l_NextDowntimeID++;
+ }
+
+ Checkable::Ptr checkable = GetCheckable();
+
+ checkable->RegisterDowntime(this);
+
+ Downtime::Ptr parent = GetByName(GetParent());
+
+ if (parent)
+ parent->RegisterChild(this);
+
+ if (runtimeCreated)
+ OnDowntimeAdded(this);
+
+ /* if this object is already in a NOT-OK state trigger
+ * this downtime now *after* it has been added (important
+ * for DB IDO, etc.)
+ */
+ if (!GetFixed() && !checkable->IsStateOK(checkable->GetStateRaw())) {
+ Log(LogNotice, "Downtime")
+ << "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
+ << " Triggering downtime now.";
+
+ TriggerDowntime(std::fmax(std::fmax(GetStartTime(), GetEntryTime()), checkable->GetLastStateChange()));
+ }
+
+ if (GetFixed() && CanBeTriggered()) {
+ /* Send notifications. */
+ OnDowntimeStarted(this);
+
+ /* Trigger fixed downtime immediately. */
+ TriggerDowntime(std::fmax(GetStartTime(), GetEntryTime()));
+ }
+}
+
+void Downtime::Stop(bool runtimeRemoved)
+{
+ GetCheckable()->UnregisterDowntime(this);
+
+ Downtime::Ptr parent = GetByName(GetParent());
+
+ if (parent)
+ parent->UnregisterChild(this);
+
+ if (runtimeRemoved)
+ OnDowntimeRemoved(this);
+
+ ObjectImpl<Downtime>::Stop(runtimeRemoved);
+}
+
+void Downtime::Pause()
+{
+ if (m_CleanupTimer) {
+ m_CleanupTimer->Stop();
+ }
+
+ ObjectImpl<Downtime>::Pause();
+}
+
+void Downtime::Resume()
+{
+ ObjectImpl<Downtime>::Resume();
+ SetupCleanupTimer();
+}
+
+Checkable::Ptr Downtime::GetCheckable() const
+{
+ return static_pointer_cast<Checkable>(m_Checkable);
+}
+
+bool Downtime::IsInEffect() const
+{
+ double now = Utility::GetTime();
+
+ if (GetFixed()) {
+ /* fixed downtimes are in effect during the entire [start..end) interval */
+ return (now >= GetStartTime() && now < GetEndTime());
+ }
+
+ double triggerTime = GetTriggerTime();
+
+ if (triggerTime == 0)
+ /* flexible downtime has not been triggered yet */
+ return false;
+
+ return (now < triggerTime + GetDuration());
+}
+
+bool Downtime::IsTriggered() const
+{
+ double now = Utility::GetTime();
+
+ double triggerTime = GetTriggerTime();
+
+ return (triggerTime > 0 && triggerTime <= now);
+}
+
+bool Downtime::IsExpired() const
+{
+ double now = Utility::GetTime();
+
+ if (GetFixed())
+ return (GetEndTime() < now);
+ else {
+ /* triggered flexible downtime not in effect anymore */
+ if (IsTriggered() && !IsInEffect())
+ return true;
+ /* flexible downtime never triggered */
+ else if (!IsTriggered() && (GetEndTime() < now))
+ return true;
+ else
+ return false;
+ }
+}
+
+bool Downtime::HasValidConfigOwner() const
+{
+ if (!ScheduledDowntime::AllConfigIsLoaded()) {
+ return true;
+ }
+
+ String configOwner = GetConfigOwner();
+ return configOwner.IsEmpty() || Zone::GetByName(GetAuthoritativeZone()) != Zone::GetLocalZone() || GetObject<ScheduledDowntime>(configOwner);
+}
+
+int Downtime::GetNextDowntimeID()
+{
+ std::unique_lock<std::mutex> lock(l_DowntimeMutex);
+
+ return l_NextDowntimeID;
+}
+
+Downtime::Ptr Downtime::AddDowntime(const Checkable::Ptr& checkable, const String& author,
+ const String& comment, double startTime, double endTime, bool fixed,
+ const String& triggeredBy, double duration,
+ const String& scheduledDowntime, const String& scheduledBy, const String& parent,
+ const String& id, const MessageOrigin::Ptr& origin)
+{
+ String fullName;
+
+ if (id.IsEmpty())
+ fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
+ else
+ fullName = id;
+
+ Dictionary::Ptr attrs = new Dictionary();
+
+ attrs->Set("author", author);
+ attrs->Set("comment", comment);
+ attrs->Set("start_time", startTime);
+ attrs->Set("end_time", endTime);
+ attrs->Set("fixed", fixed);
+ attrs->Set("duration", duration);
+ attrs->Set("triggered_by", triggeredBy);
+ attrs->Set("scheduled_by", scheduledBy);
+ attrs->Set("parent", parent);
+ attrs->Set("config_owner", scheduledDowntime);
+ attrs->Set("entry_time", Utility::GetTime());
+
+ if (!scheduledDowntime.IsEmpty()) {
+ auto localZone (Zone::GetLocalZone());
+
+ if (localZone) {
+ attrs->Set("authoritative_zone", localZone->GetName());
+ }
+
+ auto sd (ScheduledDowntime::GetByName(scheduledDowntime));
+
+ if (sd) {
+ attrs->Set("config_owner_hash", sd->HashDowntimeOptions());
+ }
+ }
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ attrs->Set("host_name", host->GetName());
+ if (service)
+ attrs->Set("service_name", service->GetShortName());
+
+ String zone;
+
+ if (!scheduledDowntime.IsEmpty()) {
+ auto sdt (ScheduledDowntime::GetByName(scheduledDowntime));
+
+ if (sdt) {
+ auto sdtZone (sdt->GetZone());
+
+ if (sdtZone) {
+ zone = sdtZone->GetName();
+ }
+ }
+ }
+
+ if (zone.IsEmpty()) {
+ zone = checkable->GetZoneName();
+ }
+
+ if (!zone.IsEmpty())
+ attrs->Set("zone", zone);
+
+ String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, nullptr, attrs);
+
+ Array::Ptr errors = new Array();
+
+ if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors, nullptr)) {
+ ObjectLock olock(errors);
+ for (const String& error : errors) {
+ Log(LogCritical, "Downtime", error);
+ }
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime."));
+ }
+
+ if (!triggeredBy.IsEmpty()) {
+ Downtime::Ptr parentDowntime = Downtime::GetByName(triggeredBy);
+ Array::Ptr triggers = parentDowntime->GetTriggers();
+
+ ObjectLock olock(triggers);
+ if (!triggers->Contains(fullName))
+ triggers->Add(fullName);
+ }
+
+ Downtime::Ptr downtime = Downtime::GetByName(fullName);
+
+ if (!downtime)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime object."));
+
+ Log(LogInformation, "Downtime")
+ << "Added downtime '" << downtime->GetName()
+ << "' between '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", startTime)
+ << "' and '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endTime) << "', author: '"
+ << author << "', " << (fixed ? "fixed" : "flexible with " + Convert::ToString(duration) + "s duration");
+
+ return downtime;
+}
+
+void Downtime::RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired,
+ const String& removedBy, const MessageOrigin::Ptr& origin)
+{
+ Downtime::Ptr downtime = Downtime::GetByName(id);
+
+ if (!downtime || downtime->GetPackage() != "_api")
+ return;
+
+ String config_owner = downtime->GetConfigOwner();
+
+ if (!config_owner.IsEmpty() && !expired) {
+ BOOST_THROW_EXCEPTION(invalid_downtime_removal_error("Cannot remove downtime '" + downtime->GetName() +
+ "'. It is owned by scheduled downtime object '" + config_owner + "'"));
+ }
+
+ if (includeChildren) {
+ for (const Downtime::Ptr& child : downtime->GetChildren()) {
+ Downtime::RemoveDowntime(child->GetName(), true, true);
+ }
+ }
+
+ if (cancelled) {
+ downtime->SetRemovalInfo(removedBy, Utility::GetTime());
+ }
+
+ Array::Ptr errors = new Array();
+
+ if (!ConfigObjectUtility::DeleteObject(downtime, false, errors, nullptr)) {
+ ObjectLock olock(errors);
+ for (const String& error : errors) {
+ Log(LogCritical, "Downtime", error);
+ }
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
+ }
+
+ String reason;
+
+ if (expired) {
+ reason = "expired at " + Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", downtime->GetEndTime());
+ } else if (cancelled) {
+ reason = "cancelled by user";
+ } else {
+ reason = "<unknown>";
+ }
+
+ Log msg (LogInformation, "Downtime");
+
+ msg << "Removed downtime '" << downtime->GetName() << "' from checkable";
+
+ {
+ auto checkable (downtime->GetCheckable());
+
+ if (checkable) {
+ msg << " '" << checkable->GetName() << "'";
+ }
+ }
+
+ msg << " (Reason: " << reason << ").";
+}
+
+void Downtime::RegisterChild(const Downtime::Ptr& downtime)
+{
+ std::unique_lock<std::mutex> lock(m_ChildrenMutex);
+ m_Children.insert(downtime);
+}
+
+void Downtime::UnregisterChild(const Downtime::Ptr& downtime)
+{
+ std::unique_lock<std::mutex> lock(m_ChildrenMutex);
+ m_Children.erase(downtime);
+}
+
+std::set<Downtime::Ptr> Downtime::GetChildren() const
+{
+ std::unique_lock<std::mutex> lock(m_ChildrenMutex);
+ return m_Children;
+}
+
+bool Downtime::CanBeTriggered()
+{
+ if (IsInEffect() && IsTriggered())
+ return false;
+
+ if (IsExpired())
+ return false;
+
+ double now = Utility::GetTime();
+
+ if (now < GetStartTime() || now > GetEndTime())
+ return false;
+
+ return true;
+}
+
+void Downtime::SetupCleanupTimer()
+{
+ if (!m_CleanupTimer) {
+ m_CleanupTimer = Timer::Create();
+
+ auto name (GetName());
+
+ m_CleanupTimer->OnTimerExpired.connect([name=std::move(name)](const Timer * const&) {
+ auto downtime (Downtime::GetByName(name));
+
+ if (downtime && downtime->IsExpired()) {
+ RemoveDowntime(name, false, false, true);
+ }
+ });
+ }
+
+ auto triggerTime (GetTriggerTime());
+
+ m_CleanupTimer->Reschedule((GetFixed() || triggerTime <= 0 ? GetEndTime() : triggerTime + GetDuration()) + 0.1);
+ m_CleanupTimer->Start();
+}
+
+void Downtime::TriggerDowntime(double triggerTime)
+{
+ if (!CanBeTriggered())
+ return;
+
+ Checkable::Ptr checkable = GetCheckable();
+
+ Log(LogInformation, "Downtime")
+ << "Triggering downtime '" << GetName() << "' for checkable '" << checkable->GetName() << "'.";
+
+ if (GetTriggerTime() == 0) {
+ SetTriggerTime(triggerTime);
+ }
+
+ {
+ ObjectLock olock (this);
+ SetupCleanupTimer();
+ }
+
+ Array::Ptr triggers = GetTriggers();
+
+ {
+ ObjectLock olock(triggers);
+ for (const String& triggerName : triggers) {
+ Downtime::Ptr downtime = Downtime::GetByName(triggerName);
+
+ if (!downtime)
+ continue;
+
+ downtime->TriggerDowntime(triggerTime);
+ }
+ }
+
+ OnDowntimeTriggered(this);
+}
+
+void Downtime::SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin) {
+ {
+ ObjectLock olock(this);
+
+ SetRemovedBy(removedBy, false, origin);
+ SetRemoveTime(removeTime, false, origin);
+ }
+
+ OnRemovalInfoChanged(this, removedBy, removeTime, origin);
+}
+
+String Downtime::GetDowntimeIDFromLegacyID(int id)
+{
+ std::unique_lock<std::mutex> lock(l_DowntimeMutex);
+
+ auto it = l_LegacyDowntimesCache.find(id);
+
+ if (it == l_LegacyDowntimesCache.end())
+ return Empty;
+
+ return it->second;
+}
+
+void Downtime::DowntimesStartTimerHandler()
+{
+ /* Start fixed downtimes. Flexible downtimes will be triggered on-demand. */
+ for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
+ if (downtime->IsActive() &&
+ downtime->CanBeTriggered() &&
+ downtime->GetFixed()) {
+ /* Send notifications. */
+ OnDowntimeStarted(downtime);
+
+ /* Trigger fixed downtime immediately. */
+ downtime->TriggerDowntime(std::fmax(downtime->GetStartTime(), downtime->GetEntryTime()));
+ }
+ }
+}
+
+void Downtime::DowntimesOrphanedTimerHandler()
+{
+ for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
+ /* Only remove downtimes which are activated after daemon start. */
+ if (downtime->IsActive() && !downtime->HasValidConfigOwner())
+ RemoveDowntime(downtime->GetName(), false, false, true);
+ }
+}
+
+void Downtime::ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Downtime>::ValidateStartTime(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "start_time" }, "Start time must be greater than 0."));
+}
+
+void Downtime::ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Downtime>::ValidateEndTime(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "end_time" }, "End time must be greater than 0."));
+}
+
+DowntimeChildOptions Downtime::ChildOptionsFromValue(const Value& options)
+{
+ if (options == "DowntimeNoChildren")
+ return DowntimeNoChildren;
+ else if (options == "DowntimeTriggeredChildren")
+ return DowntimeTriggeredChildren;
+ else if (options == "DowntimeNonTriggeredChildren")
+ return DowntimeNonTriggeredChildren;
+ else if (options.IsNumber()) {
+ int number = options;
+ if (number >= 0 && number <= 2)
+ return static_cast<DowntimeChildOptions>(number);
+ }
+
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid child option specified"));
+}
diff --git a/lib/icinga/downtime.hpp b/lib/icinga/downtime.hpp
new file mode 100644
index 0000000..15aa0af
--- /dev/null
+++ b/lib/icinga/downtime.hpp
@@ -0,0 +1,99 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef DOWNTIME_H
+#define DOWNTIME_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/downtime-ti.hpp"
+#include "icinga/checkable-ti.hpp"
+#include "remote/messageorigin.hpp"
+
+namespace icinga
+{
+
+enum DowntimeChildOptions
+{
+ DowntimeNoChildren,
+ DowntimeTriggeredChildren,
+ DowntimeNonTriggeredChildren
+};
+
+/**
+ * A downtime.
+ *
+ * @ingroup icinga
+ */
+class Downtime final : public ObjectImpl<Downtime>
+{
+public:
+ DECLARE_OBJECT(Downtime);
+ DECLARE_OBJECTNAME(Downtime);
+
+ static boost::signals2::signal<void (const Downtime::Ptr&)> OnDowntimeAdded;
+ static boost::signals2::signal<void (const Downtime::Ptr&)> OnDowntimeRemoved;
+ static boost::signals2::signal<void (const Downtime::Ptr&)> OnDowntimeStarted;
+ static boost::signals2::signal<void (const Downtime::Ptr&)> OnDowntimeTriggered;
+ static boost::signals2::signal<void (const Downtime::Ptr&, const String&, double, const MessageOrigin::Ptr&)> OnRemovalInfoChanged;
+
+ intrusive_ptr<Checkable> GetCheckable() const;
+
+ bool IsInEffect() const;
+ bool IsTriggered() const;
+ bool IsExpired() const;
+ bool HasValidConfigOwner() const;
+
+ static void StaticInitialize();
+
+ static int GetNextDowntimeID();
+
+ static Ptr AddDowntime(const intrusive_ptr<Checkable>& checkable, const String& author,
+ const String& comment, double startTime, double endTime, bool fixed,
+ const String& triggeredBy, double duration, const String& scheduledDowntime = String(),
+ const String& scheduledBy = String(), const String& parent = String(), const String& id = String(),
+ const MessageOrigin::Ptr& origin = nullptr);
+
+ static void RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired = false,
+ const String& removedBy = "", const MessageOrigin::Ptr& origin = nullptr);
+
+ void RegisterChild(const Downtime::Ptr& downtime);
+ void UnregisterChild(const Downtime::Ptr& downtime);
+ std::set<Downtime::Ptr> GetChildren() const;
+
+ void TriggerDowntime(double triggerTime);
+ void SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin = nullptr);
+
+ void OnAllConfigLoaded() override;
+
+ static String GetDowntimeIDFromLegacyID(int id);
+
+ static DowntimeChildOptions ChildOptionsFromValue(const Value& options);
+
+protected:
+ void Start(bool runtimeCreated) override;
+ void Stop(bool runtimeRemoved) override;
+
+ void Pause() override;
+ void Resume() override;
+
+ void ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils) override;
+ void ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils) override;
+
+private:
+ ObjectImpl<Checkable>::Ptr m_Checkable;
+
+ std::set<Downtime::Ptr> m_Children;
+ mutable std::mutex m_ChildrenMutex;
+
+ Timer::Ptr m_CleanupTimer;
+
+ bool CanBeTriggered();
+
+ void SetupCleanupTimer();
+
+ static void DowntimesStartTimerHandler();
+ static void DowntimesOrphanedTimerHandler();
+};
+
+}
+
+#endif /* DOWNTIME_H */
diff --git a/lib/icinga/downtime.ti b/lib/icinga/downtime.ti
new file mode 100644
index 0000000..21e9731
--- /dev/null
+++ b/lib/icinga/downtime.ti
@@ -0,0 +1,82 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/configobject.hpp"
+#include "base/utility.hpp"
+#impl_include "icinga/service.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+class DowntimeNameComposer : public NameComposer
+{
+public:
+ virtual String MakeName(const String& shortName, const Object::Ptr& context) const;
+ virtual Dictionary::Ptr ParseName(const String& name) const;
+};
+}}}
+
+class Downtime : ConfigObject < DowntimeNameComposer
+{
+ activation_priority -10;
+
+ load_after Host;
+ load_after Service;
+
+ [config, no_user_modify, required, navigation(host)] name(Host) host_name {
+ navigate {{{
+ return Host::GetByName(GetHostName());
+ }}}
+ };
+ [config, no_user_modify, navigation(service)] String service_name {
+ track {{{
+ if (!oldValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), oldValue);
+ DependencyGraph::RemoveDependency(this, service.get());
+ }
+
+ if (!newValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), newValue);
+ DependencyGraph::AddDependency(this, service.get());
+ }
+ }}}
+ navigate {{{
+ if (GetServiceName().IsEmpty())
+ return nullptr;
+
+ Host::Ptr host = Host::GetByName(GetHostName());
+ return host->GetServiceByShortName(GetServiceName());
+ }}}
+ };
+
+ [config] Timestamp entry_time {
+ default {{{ return Utility::GetTime(); }}}
+ };
+ [config, required] String author;
+ [config, required] String comment;
+ [config] Timestamp start_time;
+ [config] Timestamp end_time;
+ [state] Timestamp trigger_time;
+ [config] bool fixed;
+ [config] Timestamp duration;
+ [config] String triggered_by;
+ [config] String scheduled_by;
+ [config] String parent;
+ [state] Array::Ptr triggers {
+ default {{{ return new Array(); }}}
+ };
+ [state] int legacy_id;
+ [state] Timestamp remove_time;
+ [no_storage] bool was_cancelled {
+ get {{{ return GetRemoveTime() > 0; }}}
+ };
+ [config] String config_owner;
+ [config] String config_owner_hash;
+ [config] String authoritative_zone;
+
+ [no_user_view, no_user_modify] String removed_by;
+};
+
+}
diff --git a/lib/icinga/envresolver.cpp b/lib/icinga/envresolver.cpp
new file mode 100644
index 0000000..633255c
--- /dev/null
+++ b/lib/icinga/envresolver.cpp
@@ -0,0 +1,20 @@
+/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+#include "base/string.hpp"
+#include "base/value.hpp"
+#include "icinga/envresolver.hpp"
+#include "icinga/checkresult.hpp"
+#include <cstdlib>
+
+using namespace icinga;
+
+bool EnvResolver::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
+{
+ auto value (getenv(macro.CStr()));
+
+ if (value) {
+ *result = value;
+ }
+
+ return value;
+}
diff --git a/lib/icinga/envresolver.hpp b/lib/icinga/envresolver.hpp
new file mode 100644
index 0000000..b3f0076
--- /dev/null
+++ b/lib/icinga/envresolver.hpp
@@ -0,0 +1,30 @@
+/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+#ifndef ENVRESOLVER_H
+#define ENVRESOLVER_H
+
+#include "base/object.hpp"
+#include "base/string.hpp"
+#include "base/value.hpp"
+#include "icinga/macroresolver.hpp"
+#include "icinga/checkresult.hpp"
+
+namespace icinga
+{
+
+/**
+ * Resolves env var names.
+ *
+ * @ingroup icinga
+ */
+class EnvResolver final : public Object, public MacroResolver
+{
+public:
+ DECLARE_PTR_TYPEDEFS(EnvResolver);
+
+ bool ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const override;
+};
+
+}
+
+#endif /* ENVRESOLVER_H */
diff --git a/lib/icinga/eventcommand.cpp b/lib/icinga/eventcommand.cpp
new file mode 100644
index 0000000..39f2d31
--- /dev/null
+++ b/lib/icinga/eventcommand.cpp
@@ -0,0 +1,20 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/eventcommand.hpp"
+#include "icinga/eventcommand-ti.cpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(EventCommand);
+
+thread_local EventCommand::Ptr EventCommand::ExecuteOverride;
+
+void EventCommand::Execute(const Checkable::Ptr& checkable,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros)
+{
+ GetExecute()->Invoke({
+ checkable,
+ resolvedMacros,
+ useResolvedMacros
+ });
+}
diff --git a/lib/icinga/eventcommand.hpp b/lib/icinga/eventcommand.hpp
new file mode 100644
index 0000000..67997e6
--- /dev/null
+++ b/lib/icinga/eventcommand.hpp
@@ -0,0 +1,32 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef EVENTCOMMAND_H
+#define EVENTCOMMAND_H
+
+#include "icinga/eventcommand-ti.hpp"
+#include "icinga/checkable.hpp"
+
+namespace icinga
+{
+
+/**
+ * An event handler command.
+ *
+ * @ingroup icinga
+ */
+class EventCommand final : public ObjectImpl<EventCommand>
+{
+public:
+ DECLARE_OBJECT(EventCommand);
+ DECLARE_OBJECTNAME(EventCommand);
+
+ static thread_local EventCommand::Ptr ExecuteOverride;
+
+ void Execute(const Checkable::Ptr& checkable,
+ const Dictionary::Ptr& resolvedMacros = nullptr,
+ bool useResolvedMacros = false);
+};
+
+}
+
+#endif /* EVENTCOMMAND_H */
diff --git a/lib/icinga/eventcommand.ti b/lib/icinga/eventcommand.ti
new file mode 100644
index 0000000..a166d1e
--- /dev/null
+++ b/lib/icinga/eventcommand.ti
@@ -0,0 +1,15 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/command.hpp"
+
+
+library icinga;
+
+namespace icinga
+{
+
+class EventCommand : Command
+{
+};
+
+}
diff --git a/lib/icinga/externalcommandprocessor.cpp b/lib/icinga/externalcommandprocessor.cpp
new file mode 100644
index 0000000..9850da0
--- /dev/null
+++ b/lib/icinga/externalcommandprocessor.cpp
@@ -0,0 +1,2281 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/externalcommandprocessor.hpp"
+#include "icinga/checkable.hpp"
+#include "icinga/host.hpp"
+#include "icinga/service.hpp"
+#include "icinga/user.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/servicegroup.hpp"
+#include "icinga/pluginutility.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/compatutility.hpp"
+#include "remote/apifunction.hpp"
+#include "base/convert.hpp"
+#include "base/logger.hpp"
+#include "base/objectlock.hpp"
+#include "base/application.hpp"
+#include "base/utility.hpp"
+#include "base/exception.hpp"
+#include <fstream>
+#include <boost/thread/once.hpp>
+
+using namespace icinga;
+
+boost::signals2::signal<void(double, const String&, const std::vector<String>&)> ExternalCommandProcessor::OnNewExternalCommand;
+
+void ExternalCommandProcessor::Execute(const String& line)
+{
+ if (line.IsEmpty())
+ return;
+
+ if (line[0] != '[')
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Missing timestamp in command: " + line));
+
+ size_t pos = line.FindFirstOf("]");
+
+ if (pos == String::NPos)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Missing timestamp in command: " + line));
+
+ String timestamp = line.SubStr(1, pos - 1);
+ String args = line.SubStr(pos + 2, String::NPos);
+
+ double ts = Convert::ToDouble(timestamp);
+
+ if (ts == 0)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid timestamp in command: " + line));
+
+ std::vector<String> argv = args.Split(";");
+
+ if (argv.empty())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Missing arguments in command: " + line));
+
+ std::vector<String> argvExtra(argv.begin() + 1, argv.end());
+ Execute(ts, argv[0], argvExtra);
+}
+
+void ExternalCommandProcessor::Execute(double time, const String& command, const std::vector<String>& arguments)
+{
+ ExternalCommandInfo eci;
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, []() {
+ RegisterCommands();
+ });
+
+ {
+ std::unique_lock<std::mutex> lock(GetMutex());
+
+ auto it = GetCommands().find(command);
+
+ if (it == GetCommands().end())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The external command '" + command + "' does not exist."));
+
+ eci = it->second;
+ }
+
+ if (arguments.size() < eci.MinArgs)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Expected " + Convert::ToString(eci.MinArgs) + " arguments"));
+
+ size_t argnum = std::min(arguments.size(), eci.MaxArgs);
+
+ std::vector<String> realArguments;
+ realArguments.resize(argnum);
+
+ if (argnum > 0) {
+ std::copy(arguments.begin(), arguments.begin() + argnum - 1, realArguments.begin());
+
+ String last_argument;
+ for (std::vector<String>::size_type i = argnum - 1; i < arguments.size(); i++) {
+ if (!last_argument.IsEmpty())
+ last_argument += ";";
+
+ last_argument += arguments[i];
+ }
+
+ realArguments[argnum - 1] = last_argument;
+ }
+
+ OnNewExternalCommand(time, command, realArguments);
+
+ eci.Callback(time, realArguments);
+}
+
+void ExternalCommandProcessor::RegisterCommand(const String& command, const ExternalCommandCallback& callback, size_t minArgs, size_t maxArgs)
+{
+ std::unique_lock<std::mutex> lock(GetMutex());
+ ExternalCommandInfo eci;
+ eci.Callback = callback;
+ eci.MinArgs = minArgs;
+ eci.MaxArgs = (maxArgs == UINT_MAX) ? minArgs : maxArgs;
+ GetCommands()[command] = eci;
+}
+
+void ExternalCommandProcessor::RegisterCommands()
+{
+ RegisterCommand("PROCESS_HOST_CHECK_RESULT", &ExternalCommandProcessor::ProcessHostCheckResult, 3);
+ RegisterCommand("PROCESS_SERVICE_CHECK_RESULT", &ExternalCommandProcessor::ProcessServiceCheckResult, 4);
+ RegisterCommand("SCHEDULE_HOST_CHECK", &ExternalCommandProcessor::ScheduleHostCheck, 2);
+ RegisterCommand("SCHEDULE_FORCED_HOST_CHECK", &ExternalCommandProcessor::ScheduleForcedHostCheck, 2);
+ RegisterCommand("SCHEDULE_SVC_CHECK", &ExternalCommandProcessor::ScheduleSvcCheck, 3);
+ RegisterCommand("SCHEDULE_FORCED_SVC_CHECK", &ExternalCommandProcessor::ScheduleForcedSvcCheck, 3);
+ RegisterCommand("ENABLE_HOST_CHECK", &ExternalCommandProcessor::EnableHostCheck, 1);
+ RegisterCommand("DISABLE_HOST_CHECK", &ExternalCommandProcessor::DisableHostCheck, 1);
+ RegisterCommand("ENABLE_SVC_CHECK", &ExternalCommandProcessor::EnableSvcCheck, 2);
+ RegisterCommand("DISABLE_SVC_CHECK", &ExternalCommandProcessor::DisableSvcCheck, 2);
+ RegisterCommand("SHUTDOWN_PROCESS", &ExternalCommandProcessor::ShutdownProcess);
+ RegisterCommand("RESTART_PROCESS", &ExternalCommandProcessor::RestartProcess);
+ RegisterCommand("SCHEDULE_FORCED_HOST_SVC_CHECKS", &ExternalCommandProcessor::ScheduleForcedHostSvcChecks, 2);
+ RegisterCommand("SCHEDULE_HOST_SVC_CHECKS", &ExternalCommandProcessor::ScheduleHostSvcChecks, 2);
+ RegisterCommand("ENABLE_HOST_SVC_CHECKS", &ExternalCommandProcessor::EnableHostSvcChecks, 1);
+ RegisterCommand("DISABLE_HOST_SVC_CHECKS", &ExternalCommandProcessor::DisableHostSvcChecks, 1);
+ RegisterCommand("ACKNOWLEDGE_SVC_PROBLEM", &ExternalCommandProcessor::AcknowledgeSvcProblem, 7);
+ RegisterCommand("ACKNOWLEDGE_SVC_PROBLEM_EXPIRE", &ExternalCommandProcessor::AcknowledgeSvcProblemExpire, 8);
+ RegisterCommand("REMOVE_SVC_ACKNOWLEDGEMENT", &ExternalCommandProcessor::RemoveSvcAcknowledgement, 2);
+ RegisterCommand("ACKNOWLEDGE_HOST_PROBLEM", &ExternalCommandProcessor::AcknowledgeHostProblem, 6);
+ RegisterCommand("ACKNOWLEDGE_HOST_PROBLEM_EXPIRE", &ExternalCommandProcessor::AcknowledgeHostProblemExpire, 7);
+ RegisterCommand("REMOVE_HOST_ACKNOWLEDGEMENT", &ExternalCommandProcessor::RemoveHostAcknowledgement, 1);
+ RegisterCommand("DISABLE_HOST_FLAP_DETECTION", &ExternalCommandProcessor::DisableHostFlapping, 1);
+ RegisterCommand("ENABLE_HOST_FLAP_DETECTION", &ExternalCommandProcessor::EnableHostFlapping, 1);
+ RegisterCommand("DISABLE_SVC_FLAP_DETECTION", &ExternalCommandProcessor::DisableSvcFlapping, 2);
+ RegisterCommand("ENABLE_SVC_FLAP_DETECTION", &ExternalCommandProcessor::EnableSvcFlapping, 2);
+ RegisterCommand("ENABLE_HOSTGROUP_SVC_CHECKS", &ExternalCommandProcessor::EnableHostgroupSvcChecks, 1);
+ RegisterCommand("DISABLE_HOSTGROUP_SVC_CHECKS", &ExternalCommandProcessor::DisableHostgroupSvcChecks, 1);
+ RegisterCommand("ENABLE_SERVICEGROUP_SVC_CHECKS", &ExternalCommandProcessor::EnableServicegroupSvcChecks, 1);
+ RegisterCommand("DISABLE_SERVICEGROUP_SVC_CHECKS", &ExternalCommandProcessor::DisableServicegroupSvcChecks, 1);
+ RegisterCommand("ENABLE_PASSIVE_HOST_CHECKS", &ExternalCommandProcessor::EnablePassiveHostChecks, 1);
+ RegisterCommand("DISABLE_PASSIVE_HOST_CHECKS", &ExternalCommandProcessor::DisablePassiveHostChecks, 1);
+ RegisterCommand("ENABLE_PASSIVE_SVC_CHECKS", &ExternalCommandProcessor::EnablePassiveSvcChecks, 2);
+ RegisterCommand("DISABLE_PASSIVE_SVC_CHECKS", &ExternalCommandProcessor::DisablePassiveSvcChecks, 2);
+ RegisterCommand("ENABLE_SERVICEGROUP_PASSIVE_SVC_CHECKS", &ExternalCommandProcessor::EnableServicegroupPassiveSvcChecks, 1);
+ RegisterCommand("DISABLE_SERVICEGROUP_PASSIVE_SVC_CHECKS", &ExternalCommandProcessor::DisableServicegroupPassiveSvcChecks, 1);
+ RegisterCommand("ENABLE_HOSTGROUP_PASSIVE_SVC_CHECKS", &ExternalCommandProcessor::EnableHostgroupPassiveSvcChecks, 1);
+ RegisterCommand("DISABLE_HOSTGROUP_PASSIVE_SVC_CHECKS", &ExternalCommandProcessor::DisableHostgroupPassiveSvcChecks, 1);
+ RegisterCommand("PROCESS_FILE", &ExternalCommandProcessor::ProcessFile, 2);
+ RegisterCommand("SCHEDULE_SVC_DOWNTIME", &ExternalCommandProcessor::ScheduleSvcDowntime, 9);
+ RegisterCommand("DEL_SVC_DOWNTIME", &ExternalCommandProcessor::DelSvcDowntime, 1);
+ RegisterCommand("SCHEDULE_HOST_DOWNTIME", &ExternalCommandProcessor::ScheduleHostDowntime, 8);
+ RegisterCommand("SCHEDULE_AND_PROPAGATE_HOST_DOWNTIME", &ExternalCommandProcessor::ScheduleAndPropagateHostDowntime, 8);
+ RegisterCommand("SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME", &ExternalCommandProcessor::ScheduleAndPropagateTriggeredHostDowntime, 8);
+ RegisterCommand("DEL_HOST_DOWNTIME", &ExternalCommandProcessor::DelHostDowntime, 1);
+ RegisterCommand("DEL_DOWNTIME_BY_HOST_NAME", &ExternalCommandProcessor::DelDowntimeByHostName, 1, 4);
+ RegisterCommand("SCHEDULE_HOST_SVC_DOWNTIME", &ExternalCommandProcessor::ScheduleHostSvcDowntime, 8);
+ RegisterCommand("SCHEDULE_HOSTGROUP_HOST_DOWNTIME", &ExternalCommandProcessor::ScheduleHostgroupHostDowntime, 8);
+ RegisterCommand("SCHEDULE_HOSTGROUP_SVC_DOWNTIME", &ExternalCommandProcessor::ScheduleHostgroupSvcDowntime, 8);
+ RegisterCommand("SCHEDULE_SERVICEGROUP_HOST_DOWNTIME", &ExternalCommandProcessor::ScheduleServicegroupHostDowntime, 8);
+ RegisterCommand("SCHEDULE_SERVICEGROUP_SVC_DOWNTIME", &ExternalCommandProcessor::ScheduleServicegroupSvcDowntime, 8);
+ RegisterCommand("ADD_HOST_COMMENT", &ExternalCommandProcessor::AddHostComment, 4);
+ RegisterCommand("DEL_HOST_COMMENT", &ExternalCommandProcessor::DelHostComment, 1);
+ RegisterCommand("ADD_SVC_COMMENT", &ExternalCommandProcessor::AddSvcComment, 5);
+ RegisterCommand("DEL_SVC_COMMENT", &ExternalCommandProcessor::DelSvcComment, 1);
+ RegisterCommand("DEL_ALL_HOST_COMMENTS", &ExternalCommandProcessor::DelAllHostComments, 1);
+ RegisterCommand("DEL_ALL_SVC_COMMENTS", &ExternalCommandProcessor::DelAllSvcComments, 2);
+ RegisterCommand("SEND_CUSTOM_HOST_NOTIFICATION", &ExternalCommandProcessor::SendCustomHostNotification, 4);
+ RegisterCommand("SEND_CUSTOM_SVC_NOTIFICATION", &ExternalCommandProcessor::SendCustomSvcNotification, 5);
+ RegisterCommand("DELAY_HOST_NOTIFICATION", &ExternalCommandProcessor::DelayHostNotification, 2);
+ RegisterCommand("DELAY_SVC_NOTIFICATION", &ExternalCommandProcessor::DelaySvcNotification, 3);
+ RegisterCommand("ENABLE_HOST_NOTIFICATIONS", &ExternalCommandProcessor::EnableHostNotifications, 1);
+ RegisterCommand("DISABLE_HOST_NOTIFICATIONS", &ExternalCommandProcessor::DisableHostNotifications, 1);
+ RegisterCommand("ENABLE_SVC_NOTIFICATIONS", &ExternalCommandProcessor::EnableSvcNotifications, 2);
+ RegisterCommand("DISABLE_SVC_NOTIFICATIONS", &ExternalCommandProcessor::DisableSvcNotifications, 2);
+ RegisterCommand("ENABLE_HOST_SVC_NOTIFICATIONS", &ExternalCommandProcessor::EnableHostSvcNotifications, 1);
+ RegisterCommand("DISABLE_HOST_SVC_NOTIFICATIONS", &ExternalCommandProcessor::DisableHostSvcNotifications, 1);
+ RegisterCommand("DISABLE_HOSTGROUP_HOST_CHECKS", &ExternalCommandProcessor::DisableHostgroupHostChecks, 1);
+ RegisterCommand("DISABLE_HOSTGROUP_PASSIVE_HOST_CHECKS", &ExternalCommandProcessor::DisableHostgroupPassiveHostChecks, 1);
+ RegisterCommand("DISABLE_SERVICEGROUP_HOST_CHECKS", &ExternalCommandProcessor::DisableServicegroupHostChecks, 1);
+ RegisterCommand("DISABLE_SERVICEGROUP_PASSIVE_HOST_CHECKS", &ExternalCommandProcessor::DisableServicegroupPassiveHostChecks, 1);
+ RegisterCommand("ENABLE_HOSTGROUP_HOST_CHECKS", &ExternalCommandProcessor::EnableHostgroupHostChecks, 1);
+ RegisterCommand("ENABLE_HOSTGROUP_PASSIVE_HOST_CHECKS", &ExternalCommandProcessor::EnableHostgroupPassiveHostChecks, 1);
+ RegisterCommand("ENABLE_SERVICEGROUP_HOST_CHECKS", &ExternalCommandProcessor::EnableServicegroupHostChecks, 1);
+ RegisterCommand("ENABLE_SERVICEGROUP_PASSIVE_HOST_CHECKS", &ExternalCommandProcessor::EnableServicegroupPassiveHostChecks, 1);
+ RegisterCommand("ENABLE_NOTIFICATIONS", &ExternalCommandProcessor::EnableNotifications);
+ RegisterCommand("DISABLE_NOTIFICATIONS", &ExternalCommandProcessor::DisableNotifications);
+ RegisterCommand("ENABLE_FLAP_DETECTION", &ExternalCommandProcessor::EnableFlapDetection);
+ RegisterCommand("DISABLE_FLAP_DETECTION", &ExternalCommandProcessor::DisableFlapDetection);
+ RegisterCommand("ENABLE_EVENT_HANDLERS", &ExternalCommandProcessor::EnableEventHandlers);
+ RegisterCommand("DISABLE_EVENT_HANDLERS", &ExternalCommandProcessor::DisableEventHandlers);
+ RegisterCommand("ENABLE_PERFORMANCE_DATA", &ExternalCommandProcessor::EnablePerformanceData);
+ RegisterCommand("DISABLE_PERFORMANCE_DATA", &ExternalCommandProcessor::DisablePerformanceData);
+ RegisterCommand("START_EXECUTING_SVC_CHECKS", &ExternalCommandProcessor::StartExecutingSvcChecks);
+ RegisterCommand("STOP_EXECUTING_SVC_CHECKS", &ExternalCommandProcessor::StopExecutingSvcChecks);
+ RegisterCommand("START_EXECUTING_HOST_CHECKS", &ExternalCommandProcessor::StartExecutingHostChecks);
+ RegisterCommand("STOP_EXECUTING_HOST_CHECKS", &ExternalCommandProcessor::StopExecutingHostChecks);
+ RegisterCommand("CHANGE_NORMAL_SVC_CHECK_INTERVAL", &ExternalCommandProcessor::ChangeNormalSvcCheckInterval, 3);
+ RegisterCommand("CHANGE_NORMAL_HOST_CHECK_INTERVAL", &ExternalCommandProcessor::ChangeNormalHostCheckInterval, 2);
+ RegisterCommand("CHANGE_RETRY_SVC_CHECK_INTERVAL", &ExternalCommandProcessor::ChangeRetrySvcCheckInterval, 3);
+ RegisterCommand("CHANGE_RETRY_HOST_CHECK_INTERVAL", &ExternalCommandProcessor::ChangeRetryHostCheckInterval, 2);
+ RegisterCommand("ENABLE_HOST_EVENT_HANDLER", &ExternalCommandProcessor::EnableHostEventHandler, 1);
+ RegisterCommand("DISABLE_HOST_EVENT_HANDLER", &ExternalCommandProcessor::DisableHostEventHandler, 1);
+ RegisterCommand("ENABLE_SVC_EVENT_HANDLER", &ExternalCommandProcessor::EnableSvcEventHandler, 2);
+ RegisterCommand("DISABLE_SVC_EVENT_HANDLER", &ExternalCommandProcessor::DisableSvcEventHandler, 2);
+ RegisterCommand("CHANGE_HOST_EVENT_HANDLER", &ExternalCommandProcessor::ChangeHostEventHandler, 2);
+ RegisterCommand("CHANGE_SVC_EVENT_HANDLER", &ExternalCommandProcessor::ChangeSvcEventHandler, 3);
+ RegisterCommand("CHANGE_HOST_CHECK_COMMAND", &ExternalCommandProcessor::ChangeHostCheckCommand, 2);
+ RegisterCommand("CHANGE_SVC_CHECK_COMMAND", &ExternalCommandProcessor::ChangeSvcCheckCommand, 3);
+ RegisterCommand("CHANGE_MAX_HOST_CHECK_ATTEMPTS", &ExternalCommandProcessor::ChangeMaxHostCheckAttempts, 2);
+ RegisterCommand("CHANGE_MAX_SVC_CHECK_ATTEMPTS", &ExternalCommandProcessor::ChangeMaxSvcCheckAttempts, 3);
+ RegisterCommand("CHANGE_HOST_CHECK_TIMEPERIOD", &ExternalCommandProcessor::ChangeHostCheckTimeperiod, 2);
+ RegisterCommand("CHANGE_SVC_CHECK_TIMEPERIOD", &ExternalCommandProcessor::ChangeSvcCheckTimeperiod, 3);
+ RegisterCommand("CHANGE_CUSTOM_HOST_VAR", &ExternalCommandProcessor::ChangeCustomHostVar, 3);
+ RegisterCommand("CHANGE_CUSTOM_SVC_VAR", &ExternalCommandProcessor::ChangeCustomSvcVar, 4);
+ RegisterCommand("CHANGE_CUSTOM_USER_VAR", &ExternalCommandProcessor::ChangeCustomUserVar, 3);
+ RegisterCommand("CHANGE_CUSTOM_CHECKCOMMAND_VAR", &ExternalCommandProcessor::ChangeCustomCheckcommandVar, 3);
+ RegisterCommand("CHANGE_CUSTOM_EVENTCOMMAND_VAR", &ExternalCommandProcessor::ChangeCustomEventcommandVar, 3);
+ RegisterCommand("CHANGE_CUSTOM_NOTIFICATIONCOMMAND_VAR", &ExternalCommandProcessor::ChangeCustomNotificationcommandVar, 3);
+
+ RegisterCommand("ENABLE_HOSTGROUP_HOST_NOTIFICATIONS", &ExternalCommandProcessor::EnableHostgroupHostNotifications, 1);
+ RegisterCommand("ENABLE_HOSTGROUP_SVC_NOTIFICATIONS", &ExternalCommandProcessor::EnableHostgroupSvcNotifications, 1);
+ RegisterCommand("DISABLE_HOSTGROUP_HOST_NOTIFICATIONS", &ExternalCommandProcessor::DisableHostgroupHostNotifications, 1);
+ RegisterCommand("DISABLE_HOSTGROUP_SVC_NOTIFICATIONS", &ExternalCommandProcessor::DisableHostgroupSvcNotifications, 1);
+ RegisterCommand("ENABLE_SERVICEGROUP_HOST_NOTIFICATIONS", &ExternalCommandProcessor::EnableServicegroupHostNotifications, 1);
+ RegisterCommand("DISABLE_SERVICEGROUP_HOST_NOTIFICATIONS", &ExternalCommandProcessor::DisableServicegroupHostNotifications, 1);
+ RegisterCommand("ENABLE_SERVICEGROUP_SVC_NOTIFICATIONS", &ExternalCommandProcessor::EnableServicegroupSvcNotifications, 1);
+ RegisterCommand("DISABLE_SERVICEGROUP_SVC_NOTIFICATIONS", &ExternalCommandProcessor::DisableServicegroupSvcNotifications, 1);
+}
+
+void ExternalCommandProcessor::ExecuteFromFile(const String& line, std::deque< std::vector<String> >& file_queue)
+{
+ if (line.IsEmpty())
+ return;
+
+ if (line[0] != '[')
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Missing timestamp in command: " + line));
+
+ size_t pos = line.FindFirstOf("]");
+
+ if (pos == String::NPos)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Missing timestamp in command: " + line));
+
+ String timestamp = line.SubStr(1, pos - 1);
+ String args = line.SubStr(pos + 2, String::NPos);
+
+ double ts = Convert::ToDouble(timestamp);
+
+ if (ts == 0)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid timestamp in command: " + line));
+
+ std::vector<String> argv = args.Split(";");
+
+ if (argv.empty())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Missing arguments in command: " + line));
+
+ std::vector<String> argvExtra(argv.begin() + 1, argv.end());
+
+ if (argv[0] == "PROCESS_FILE") {
+ Log(LogDebug, "ExternalCommandProcessor")
+ << "Enqueing external command file " << argvExtra[0];
+ file_queue.push_back(argvExtra);
+ } else {
+ Execute(ts, argv[0], argvExtra);
+ }
+}
+
+void ExternalCommandProcessor::ProcessHostCheckResult(double time, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot process passive host check result for non-existent host '" + arguments[0] + "'"));
+
+ if (!host->GetEnablePassiveChecks())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Got passive check result for host '" + arguments[0] + "' which has passive checks disabled."));
+
+ if (!host->IsReachable(DependencyCheckExecution)) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Ignoring passive check result for unreachable host '" << arguments[0] << "'";
+ return;
+ }
+
+ int exitStatus = Convert::ToDouble(arguments[1]);
+ CheckResult::Ptr result = new CheckResult();
+ std::pair<String, String> co = PluginUtility::ParseCheckOutput(arguments[2]);
+ result->SetOutput(co.first);
+ result->SetPerformanceData(PluginUtility::SplitPerfdata(co.second));
+
+ ServiceState state;
+
+ if (exitStatus == 0)
+ state = ServiceOK;
+ else if (exitStatus == 1)
+ state = ServiceCritical;
+ else
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid status code: " + arguments[1]));
+
+ result->SetState(state);
+
+ result->SetScheduleStart(time);
+ result->SetScheduleEnd(time);
+ result->SetExecutionStart(time);
+ result->SetExecutionEnd(time);
+
+ /* Mark this check result as passive. */
+ result->SetActive(false);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Processing passive check result for host '" << arguments[0] << "'";
+
+ host->ProcessCheckResult(result);
+}
+
+void ExternalCommandProcessor::ProcessServiceCheckResult(double time, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot process passive service check result for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ if (!service->GetEnablePassiveChecks())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Got passive check result for service '" + arguments[1] + "' which has passive checks disabled."));
+
+ if (!service->IsReachable(DependencyCheckExecution)) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Ignoring passive check result for unreachable service '" << arguments[1] << "'";
+ return;
+ }
+
+ int exitStatus = Convert::ToDouble(arguments[2]);
+ CheckResult::Ptr result = new CheckResult();
+ String output = CompatUtility::UnEscapeString(arguments[3]);
+ std::pair<String, String> co = PluginUtility::ParseCheckOutput(output);
+ result->SetOutput(co.first);
+ result->SetPerformanceData(PluginUtility::SplitPerfdata(co.second));
+ result->SetState(PluginUtility::ExitStatusToState(exitStatus));
+
+ result->SetScheduleStart(time);
+ result->SetScheduleEnd(time);
+ result->SetExecutionStart(time);
+ result->SetExecutionEnd(time);
+
+ /* Mark this check result as passive. */
+ result->SetActive(false);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Processing passive check result for service '" << arguments[1] << "'";
+
+ service->ProcessCheckResult(result);
+}
+
+void ExternalCommandProcessor::ScheduleHostCheck(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot reschedule host check for non-existent host '" + arguments[0] + "'"));
+
+ double planned_check = Convert::ToDouble(arguments[1]);
+
+ if (planned_check > host->GetNextCheck()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Ignoring reschedule request for host '"
+ << arguments[0] << "' (next check is already sooner than requested check time)";
+ return;
+ }
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Rescheduling next check for host '" << arguments[0] << "'";
+
+ if (planned_check < Utility::GetTime())
+ planned_check = Utility::GetTime();
+
+ host->SetNextCheck(planned_check);
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(host);
+}
+
+void ExternalCommandProcessor::ScheduleForcedHostCheck(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot reschedule forced host check for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Rescheduling next check for host '" << arguments[0] << "'";
+
+ host->SetForceNextCheck(true);
+ host->SetNextCheck(Convert::ToDouble(arguments[1]));
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(host);
+}
+
+void ExternalCommandProcessor::ScheduleSvcCheck(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot reschedule service check for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ double planned_check = Convert::ToDouble(arguments[2]);
+
+ if (planned_check > service->GetNextCheck()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Ignoring reschedule request for service '"
+ << arguments[1] << "' (next check is already sooner than requested check time)";
+ return;
+ }
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Rescheduling next check for service '" << arguments[1] << "'";
+
+ if (planned_check < Utility::GetTime())
+ planned_check = Utility::GetTime();
+
+ service->SetNextCheck(planned_check);
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(service);
+}
+
+void ExternalCommandProcessor::ScheduleForcedSvcCheck(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot reschedule forced service check for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Rescheduling next check for service '" << arguments[1] << "'";
+
+ service->SetForceNextCheck(true);
+ service->SetNextCheck(Convert::ToDouble(arguments[2]));
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(service);
+}
+
+void ExternalCommandProcessor::EnableHostCheck(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable host checks for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_active_checks", true);
+}
+
+void ExternalCommandProcessor::DisableHostCheck(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable host check non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_active_checks", false);
+}
+
+void ExternalCommandProcessor::EnableSvcCheck(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable service check for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_active_checks", true);
+}
+
+void ExternalCommandProcessor::DisableSvcCheck(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable service check for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_active_checks", false);
+}
+
+void ExternalCommandProcessor::ShutdownProcess(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Shutting down Icinga via external command.");
+ Application::RequestShutdown();
+}
+
+void ExternalCommandProcessor::RestartProcess(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Restarting Icinga via external command.");
+ Application::RequestRestart();
+}
+
+void ExternalCommandProcessor::ScheduleForcedHostSvcChecks(double, const std::vector<String>& arguments)
+{
+ double planned_check = Convert::ToDouble(arguments[1]);
+
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot reschedule forced host service checks for non-existent host '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Rescheduling next check for service '" << service->GetName() << "'";
+
+ service->SetNextCheck(planned_check);
+ service->SetForceNextCheck(true);
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(service);
+ }
+}
+
+void ExternalCommandProcessor::ScheduleHostSvcChecks(double, const std::vector<String>& arguments)
+{
+ double planned_check = Convert::ToDouble(arguments[1]);
+
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot reschedule host service checks for non-existent host '" + arguments[0] + "'"));
+
+ if (planned_check < Utility::GetTime())
+ planned_check = Utility::GetTime();
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (planned_check > service->GetNextCheck()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Ignoring reschedule request for service '"
+ << service->GetName() << "' (next check is already sooner than requested check time)";
+ continue;
+ }
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Rescheduling next check for service '" << service->GetName() << "'";
+
+ service->SetNextCheck(planned_check);
+
+ /* trigger update event for DB IDO */
+ Checkable::OnNextCheckUpdated(service);
+ }
+}
+
+void ExternalCommandProcessor::EnableHostSvcChecks(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable host service checks for non-existent host '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_active_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::DisableHostSvcChecks(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable host service checks for non-existent host '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_active_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::AcknowledgeSvcProblem(double, const std::vector<String>& arguments)
+{
+ bool sticky = (Convert::ToLong(arguments[2]) == 2 ? true : false);
+ bool notify = (Convert::ToLong(arguments[3]) > 0 ? true : false);
+ bool persistent = (Convert::ToLong(arguments[4]) > 0 ? true : false);
+
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+ ObjectLock oLock (service);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot acknowledge service problem for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ if (service->GetState() == ServiceOK)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The service '" + arguments[1] + "' is OK."));
+
+ if (service->IsAcknowledged()) {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The service '" + arguments[1] + "' is already acknowledged."));
+ }
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Setting acknowledgement for service '" << service->GetName() << "'" << (notify ? "" : ". Disabled notification");
+
+ Comment::AddComment(service, CommentAcknowledgement, arguments[5], arguments[6], persistent, 0, sticky);
+ service->AcknowledgeProblem(arguments[5], arguments[6], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent);
+}
+
+void ExternalCommandProcessor::AcknowledgeSvcProblemExpire(double, const std::vector<String>& arguments)
+{
+ bool sticky = (Convert::ToLong(arguments[2]) == 2 ? true : false);
+ bool notify = (Convert::ToLong(arguments[3]) > 0 ? true : false);
+ bool persistent = (Convert::ToLong(arguments[4]) > 0 ? true : false);
+ double timestamp = Convert::ToDouble(arguments[5]);
+
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+ ObjectLock oLock (service);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot acknowledge service problem with expire time for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ if (service->GetState() == ServiceOK)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The service '" + arguments[1] + "' is OK."));
+
+ if (timestamp != 0 && timestamp <= Utility::GetTime())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Acknowledgement expire time must be in the future for service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ if (service->IsAcknowledged()) {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The service '" + arguments[1] + "' is already acknowledged."));
+ }
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Setting timed acknowledgement for service '" << service->GetName() << "'" << (notify ? "" : ". Disabled notification");
+
+ Comment::AddComment(service, CommentAcknowledgement, arguments[6], arguments[7], persistent, timestamp, sticky);
+ service->AcknowledgeProblem(arguments[6], arguments[7], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent, timestamp);
+}
+
+void ExternalCommandProcessor::RemoveSvcAcknowledgement(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot remove service acknowledgement for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removing acknowledgement for service '" << service->GetName() << "'";
+
+ {
+ ObjectLock olock(service);
+ service->ClearAcknowledgement("");
+ }
+
+ service->RemoveAckComments();
+}
+
+void ExternalCommandProcessor::AcknowledgeHostProblem(double, const std::vector<String>& arguments)
+{
+ bool sticky = (Convert::ToLong(arguments[1]) == 2 ? true : false);
+ bool notify = (Convert::ToLong(arguments[2]) > 0 ? true : false);
+ bool persistent = (Convert::ToLong(arguments[3]) > 0 ? true : false);
+
+ Host::Ptr host = Host::GetByName(arguments[0]);
+ ObjectLock oLock (host);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot acknowledge host problem for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Setting acknowledgement for host '" << host->GetName() << "'" << (notify ? "" : ". Disabled notification");
+
+ if (host->GetState() == HostUp)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The host '" + arguments[0] + "' is OK."));
+
+ if (host->IsAcknowledged()) {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The host '" + arguments[1] + "' is already acknowledged."));
+ }
+
+ Comment::AddComment(host, CommentAcknowledgement, arguments[4], arguments[5], persistent, 0, sticky);
+ host->AcknowledgeProblem(arguments[4], arguments[5], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent);
+}
+
+void ExternalCommandProcessor::AcknowledgeHostProblemExpire(double, const std::vector<String>& arguments)
+{
+ bool sticky = (Convert::ToLong(arguments[1]) == 2 ? true : false);
+ bool notify = (Convert::ToLong(arguments[2]) > 0 ? true : false);
+ bool persistent = (Convert::ToLong(arguments[3]) > 0 ? true : false);
+ double timestamp = Convert::ToDouble(arguments[4]);
+
+ Host::Ptr host = Host::GetByName(arguments[0]);
+ ObjectLock oLock (host);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot acknowledge host problem with expire time for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Setting timed acknowledgement for host '" << host->GetName() << "'" << (notify ? "" : ". Disabled notification");
+
+ if (host->GetState() == HostUp)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The host '" + arguments[0] + "' is OK."));
+
+ if (timestamp != 0 && timestamp <= Utility::GetTime())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Acknowledgement expire time must be in the future for host '" + arguments[0] + "'"));
+
+ if (host->IsAcknowledged()) {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("The host '" + arguments[1] + "' is already acknowledged."));
+ }
+
+ Comment::AddComment(host, CommentAcknowledgement, arguments[5], arguments[6], persistent, timestamp, sticky);
+ host->AcknowledgeProblem(arguments[5], arguments[6], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent, timestamp);
+}
+
+void ExternalCommandProcessor::RemoveHostAcknowledgement(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot remove acknowledgement for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removing acknowledgement for host '" << host->GetName() << "'";
+
+ {
+ ObjectLock olock(host);
+ host->ClearAcknowledgement("");
+ }
+ host->RemoveAckComments();
+}
+
+void ExternalCommandProcessor::EnableHostgroupSvcChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable hostgroup service checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_active_checks", true);
+ }
+ }
+}
+
+void ExternalCommandProcessor::DisableHostgroupSvcChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable hostgroup service checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_active_checks", false);
+ }
+ }
+}
+
+void ExternalCommandProcessor::EnableServicegroupSvcChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable servicegroup service checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_active_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::DisableServicegroupSvcChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable servicegroup service checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_active_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::EnablePassiveHostChecks(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable passive host checks for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling passive checks for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_passive_checks", true);
+}
+
+void ExternalCommandProcessor::DisablePassiveHostChecks(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable passive host checks for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling passive checks for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_passive_checks", false);
+}
+
+void ExternalCommandProcessor::EnablePassiveSvcChecks(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable service checks for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling passive checks for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_passive_checks", true);
+}
+
+void ExternalCommandProcessor::DisablePassiveSvcChecks(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable service checks for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling passive checks for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_passive_checks", false);
+}
+
+void ExternalCommandProcessor::EnableServicegroupPassiveSvcChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable servicegroup passive service checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling passive checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_passive_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::DisableServicegroupPassiveSvcChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable servicegroup passive service checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling passive checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_passive_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::EnableHostgroupPassiveSvcChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable hostgroup passive service checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling passive checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_passive_checks", true);
+ }
+ }
+}
+
+void ExternalCommandProcessor::DisableHostgroupPassiveSvcChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable hostgroup passive service checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling passive checks for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_passive_checks", false);
+ }
+ }
+}
+
+void ExternalCommandProcessor::ProcessFile(double, const std::vector<String>& arguments)
+{
+ std::deque< std::vector<String> > file_queue;
+ file_queue.push_back(arguments);
+
+ while (!file_queue.empty()) {
+ std::vector<String> argument = file_queue.front();
+ file_queue.pop_front();
+
+ String file = argument[0];
+ int to_delete = Convert::ToLong(argument[1]);
+
+ std::ifstream ifp;
+ ifp.exceptions(std::ifstream::badbit);
+
+ ifp.open(file.CStr(), std::ifstream::in);
+
+ while (ifp.good()) {
+ std::string line;
+ std::getline(ifp, line);
+
+ try {
+ Log(LogNotice, "compat")
+ << "Executing external command: " << line;
+
+ ExecuteFromFile(line, file_queue);
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "ExternalCommandProcessor")
+ << "External command failed: " << DiagnosticInformation(ex);
+ }
+ }
+
+ ifp.close();
+
+ if (to_delete > 0)
+ (void) unlink(file.CStr());
+ }
+}
+
+void ExternalCommandProcessor::ScheduleSvcDowntime(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule service downtime for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[5]);
+ int is_fixed = Convert::ToLong(arguments[4]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for service " << service->GetName();
+ (void) Downtime::AddDowntime(service, arguments[7], arguments[8],
+ Convert::ToDouble(arguments[2]), Convert::ToDouble(arguments[3]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[6]));
+}
+
+void ExternalCommandProcessor::DelSvcDowntime(double, const std::vector<String>& arguments)
+{
+ int id = Convert::ToLong(arguments[0]);
+ String rid = Downtime::GetDowntimeIDFromLegacyID(id);
+
+ try {
+ Downtime::RemoveDowntime(rid, false, true);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removed downtime ID " << arguments[0];
+ } catch (const invalid_downtime_removal_error& error) {
+ Log(LogWarning, "ExternalCommandProcessor") << error.what();
+ }
+}
+
+void ExternalCommandProcessor::ScheduleHostDowntime(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule host downtime for non-existent host '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for host " << host->GetName();
+
+ (void) Downtime::AddDowntime(host, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+}
+
+void ExternalCommandProcessor::ScheduleAndPropagateHostDowntime(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule and propagate host downtime for non-existent host '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for host " << host->GetName();
+
+ (void) Downtime::AddDowntime(host, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+
+ /* Schedule downtime for all child hosts */
+ for (const Checkable::Ptr& child : host->GetAllChildren()) {
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(child);
+
+ /* ignore all service children */
+ if (service)
+ continue;
+
+ (void) Downtime::AddDowntime(child, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::ScheduleAndPropagateTriggeredHostDowntime(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule and propagate triggered host downtime for non-existent host '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for host " << host->GetName();
+
+ Downtime::Ptr parentDowntime = Downtime::AddDowntime(host, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+
+ /* Schedule downtime for all child hosts and explicitely trigger them through the parent host's downtime */
+ for (const Checkable::Ptr& child : host->GetAllChildren()) {
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(child);
+
+ /* ignore all service children */
+ if (service)
+ continue;
+
+ (void) Downtime::AddDowntime(child, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), parentDowntime->GetName(), Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::DelHostDowntime(double, const std::vector<String>& arguments)
+{
+ int id = Convert::ToLong(arguments[0]);
+ String rid = Downtime::GetDowntimeIDFromLegacyID(id);
+
+ try {
+ Downtime::RemoveDowntime(rid, false, true);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removed downtime ID " << arguments[0];
+ } catch (const invalid_downtime_removal_error& error) {
+ Log(LogWarning, "ExternalCommandProcessor") << error.what();
+ }
+}
+
+void ExternalCommandProcessor::DelDowntimeByHostName(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule host services downtime for non-existent host '" + arguments[0] + "'"));
+
+ String serviceName;
+ if (arguments.size() >= 2)
+ serviceName = arguments[1];
+
+ String startTime;
+ if (arguments.size() >= 3)
+ startTime = arguments[2];
+
+ String commentString;
+ if (arguments.size() >= 4)
+ commentString = arguments[3];
+
+ if (arguments.size() > 5)
+ Log(LogWarning, "ExternalCommandProcessor")
+ << ("Ignoring additional parameters for host '" + arguments[0] + "' downtime deletion.");
+
+ for (const Downtime::Ptr& downtime : host->GetDowntimes()) {
+ try {
+ String downtimeName = downtime->GetName();
+ Downtime::RemoveDowntime(downtimeName, false, true);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removed downtime '" << downtimeName << "'.";
+ } catch (const invalid_downtime_removal_error& error) {
+ Log(LogWarning, "ExternalCommandProcessor") << error.what();
+ }
+ }
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ if (!serviceName.IsEmpty() && serviceName != service->GetName())
+ continue;
+
+ for (const Downtime::Ptr& downtime : service->GetDowntimes()) {
+ if (!startTime.IsEmpty() && downtime->GetStartTime() != Convert::ToDouble(startTime))
+ continue;
+
+ if (!commentString.IsEmpty() && downtime->GetComment() != commentString)
+ continue;
+
+ try {
+ String downtimeName = downtime->GetName();
+ Downtime::RemoveDowntime(downtimeName, false, true);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removed downtime '" << downtimeName << "'.";
+ } catch (const invalid_downtime_removal_error& error) {
+ Log(LogWarning, "ExternalCommandProcessor") << error.what();
+ }
+ }
+ }
+}
+
+void ExternalCommandProcessor::ScheduleHostSvcDowntime(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule host services downtime for non-existent host '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for host " << host->GetName();
+
+ (void) Downtime::AddDowntime(host, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for service " << service->GetName();
+ (void) Downtime::AddDowntime(service, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::ScheduleHostgroupHostDowntime(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule hostgroup host downtime for non-existent hostgroup '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for host " << host->GetName();
+
+ (void) Downtime::AddDowntime(host, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::ScheduleHostgroupSvcDowntime(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule hostgroup service downtime for non-existent hostgroup '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ /* Note: we can't just directly create downtimes for all the services by iterating
+ * over all hosts in the host group - otherwise we might end up creating multiple
+ * downtimes for some services. */
+
+ std::set<Service::Ptr> services;
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ services.insert(service);
+ }
+ }
+
+ for (const Service::Ptr& service : services) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for service " << service->GetName();
+ (void) Downtime::AddDowntime(service, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::ScheduleServicegroupHostDowntime(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule servicegroup host downtime for non-existent servicegroup '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ /* Note: we can't just directly create downtimes for all the hosts by iterating
+ * over all services in the service group - otherwise we might end up creating multiple
+ * downtimes for some hosts. */
+
+ std::set<Host::Ptr> hosts;
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+ hosts.insert(host);
+ }
+
+ for (const Host::Ptr& host : hosts) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for host " << host->GetName();
+ (void) Downtime::AddDowntime(host, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::ScheduleServicegroupSvcDowntime(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot schedule servicegroup service downtime for non-existent servicegroup '" + arguments[0] + "'"));
+
+ String triggeredBy;
+ int triggeredByLegacy = Convert::ToLong(arguments[4]);
+ int is_fixed = Convert::ToLong(arguments[3]);
+ if (triggeredByLegacy != 0)
+ triggeredBy = Downtime::GetDowntimeIDFromLegacyID(triggeredByLegacy);
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating downtime for service " << service->GetName();
+ (void) Downtime::AddDowntime(service, arguments[6], arguments[7],
+ Convert::ToDouble(arguments[1]), Convert::ToDouble(arguments[2]),
+ Convert::ToBool(is_fixed), triggeredBy, Convert::ToDouble(arguments[5]));
+ }
+}
+
+void ExternalCommandProcessor::AddHostComment(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot add host comment for non-existent host '" + arguments[0] + "'"));
+
+ if (arguments[2].IsEmpty() || arguments[3].IsEmpty())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Author and comment must not be empty"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating comment for host " << host->GetName();
+ (void) Comment::AddComment(host, CommentUser, arguments[2], arguments[3], false, 0);
+}
+
+void ExternalCommandProcessor::DelHostComment(double, const std::vector<String>& arguments)
+{
+ int id = Convert::ToLong(arguments[0]);
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removing comment ID " << arguments[0];
+ String rid = Comment::GetCommentIDFromLegacyID(id);
+ Comment::RemoveComment(rid);
+}
+
+void ExternalCommandProcessor::AddSvcComment(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot add service comment for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ if (arguments[3].IsEmpty() || arguments[4].IsEmpty())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Author and comment must not be empty"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Creating comment for service " << service->GetName();
+ (void) Comment::AddComment(service, CommentUser, arguments[3], arguments[4], false, 0);
+}
+
+void ExternalCommandProcessor::DelSvcComment(double, const std::vector<String>& arguments)
+{
+ int id = Convert::ToLong(arguments[0]);
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removing comment ID " << arguments[0];
+
+ String rid = Comment::GetCommentIDFromLegacyID(id);
+ Comment::RemoveComment(rid);
+}
+
+void ExternalCommandProcessor::DelAllHostComments(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot delete all host comments for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removing all comments for host " << host->GetName();
+ host->RemoveAllComments();
+}
+
+void ExternalCommandProcessor::DelAllSvcComments(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot delete all service comments for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Removing all comments for service " << service->GetName();
+ service->RemoveAllComments();
+}
+
+void ExternalCommandProcessor::SendCustomHostNotification(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot send custom host notification for non-existent host '" + arguments[0] + "'"));
+
+ int options = Convert::ToLong(arguments[1]);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Sending custom notification for host " << host->GetName();
+ if (options & 2) {
+ host->SetForceNextNotification(true);
+ }
+
+ Checkable::OnNotificationsRequested(host, NotificationCustom,
+ host->GetLastCheckResult(), arguments[2], arguments[3], nullptr);
+}
+
+void ExternalCommandProcessor::SendCustomSvcNotification(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot send custom service notification for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ int options = Convert::ToLong(arguments[2]);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Sending custom notification for service " << service->GetName();
+
+ if (options & 2) {
+ service->SetForceNextNotification(true);
+ }
+
+ Service::OnNotificationsRequested(service, NotificationCustom,
+ service->GetLastCheckResult(), arguments[3], arguments[4], nullptr);
+}
+
+void ExternalCommandProcessor::DelayHostNotification(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot delay host notification for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Delaying notifications for host '" << host->GetName() << "'";
+
+ for (const Notification::Ptr& notification : host->GetNotifications()) {
+ notification->SetNextNotification(Convert::ToDouble(arguments[1]));
+ }
+}
+
+void ExternalCommandProcessor::DelaySvcNotification(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot delay service notification for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Delaying notifications for service " << service->GetName();
+
+ for (const Notification::Ptr& notification : service->GetNotifications()) {
+ notification->SetNextNotification(Convert::ToDouble(arguments[2]));
+ }
+}
+
+void ExternalCommandProcessor::EnableHostNotifications(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable host notifications for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_notifications", true);
+}
+
+void ExternalCommandProcessor::DisableHostNotifications(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable host notifications for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_notifications", false);
+}
+
+void ExternalCommandProcessor::EnableSvcNotifications(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable service notifications for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_notifications", true);
+}
+
+void ExternalCommandProcessor::DisableSvcNotifications(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable service notifications for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_notifications", false);
+}
+
+void ExternalCommandProcessor::EnableHostSvcNotifications(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable notifications for all services for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for all services on host '" << arguments[0] << "'";
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_notifications", true);
+ }
+}
+
+void ExternalCommandProcessor::DisableHostSvcNotifications(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable notifications for all services for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for all services on host '" << arguments[0] << "'";
+
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_notifications", false);
+ }
+}
+
+void ExternalCommandProcessor::DisableHostgroupHostChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable hostgroup host checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_active_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::DisableHostgroupPassiveHostChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable hostgroup passive host checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling passive checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_passive_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::DisableServicegroupHostChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable servicegroup host checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling active checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_active_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::DisableServicegroupPassiveHostChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable servicegroup passive host checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling passive checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_passive_checks", false);
+ }
+}
+
+void ExternalCommandProcessor::EnableHostgroupHostChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable hostgroup host checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_active_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::EnableHostgroupPassiveHostChecks(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable hostgroup passive host checks for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling passive checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_passive_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::EnableServicegroupHostChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable servicegroup host checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling active checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_active_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::EnableServicegroupPassiveHostChecks(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable servicegroup passive host checks for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling passive checks for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_passive_checks", true);
+ }
+}
+
+void ExternalCommandProcessor::EnableHostFlapping(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable host flapping for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling flapping detection for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_flapping", true);
+}
+
+void ExternalCommandProcessor::DisableHostFlapping(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable host flapping for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling flapping detection for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_flapping", false);
+}
+
+void ExternalCommandProcessor::EnableSvcFlapping(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable service flapping for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling flapping detection for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_flapping", true);
+}
+
+void ExternalCommandProcessor::DisableSvcFlapping(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable service flapping for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling flapping detection for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_flapping", false);
+}
+
+void ExternalCommandProcessor::EnableNotifications(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally enabling notifications.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_notifications", true);
+}
+
+void ExternalCommandProcessor::DisableNotifications(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally disabling notifications.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_notifications", false);
+}
+
+void ExternalCommandProcessor::EnableFlapDetection(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally enabling flap detection.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_flapping", true);
+}
+
+void ExternalCommandProcessor::DisableFlapDetection(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally disabling flap detection.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_flapping", false);
+}
+
+void ExternalCommandProcessor::EnableEventHandlers(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally enabling event handlers.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_event_handlers", true);
+}
+
+void ExternalCommandProcessor::DisableEventHandlers(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally disabling event handlers.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_event_handlers", false);
+}
+
+void ExternalCommandProcessor::EnablePerformanceData(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally enabling performance data processing.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_perfdata", true);
+}
+
+void ExternalCommandProcessor::DisablePerformanceData(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally disabling performance data processing.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_perfdata", false);
+}
+
+void ExternalCommandProcessor::StartExecutingSvcChecks(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally enabling service checks.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_service_checks", true);
+}
+
+void ExternalCommandProcessor::StopExecutingSvcChecks(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally disabling service checks.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_service_checks", false);
+}
+
+void ExternalCommandProcessor::StartExecutingHostChecks(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally enabling host checks.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_host_checks", true);
+}
+
+void ExternalCommandProcessor::StopExecutingHostChecks(double, const std::vector<String>&)
+{
+ Log(LogNotice, "ExternalCommandProcessor", "Globally disabling host checks.");
+
+ IcingaApplication::GetInstance()->ModifyAttribute("enable_host_checks", false);
+}
+
+void ExternalCommandProcessor::ChangeNormalSvcCheckInterval(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot update check interval for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ double interval = Convert::ToDouble(arguments[2]);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Updating check interval for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("check_interval", interval * 60);
+}
+
+void ExternalCommandProcessor::ChangeNormalHostCheckInterval(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot update check interval for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Updating check interval for host '" << arguments[0] << "'";
+
+ double interval = Convert::ToDouble(arguments[1]);
+
+ host->ModifyAttribute("check_interval", interval * 60);
+}
+
+void ExternalCommandProcessor::ChangeRetrySvcCheckInterval(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot update retry interval for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ double interval = Convert::ToDouble(arguments[2]);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Updating retry interval for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("retry_interval", interval * 60);
+}
+
+void ExternalCommandProcessor::ChangeRetryHostCheckInterval(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot update retry interval for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Updating retry interval for host '" << arguments[0] << "'";
+
+ double interval = Convert::ToDouble(arguments[1]);
+
+ host->ModifyAttribute("retry_interval", interval * 60);
+}
+
+void ExternalCommandProcessor::EnableHostEventHandler(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable event handler for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling event handler for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_event_handler", true);
+}
+
+void ExternalCommandProcessor::DisableHostEventHandler(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable event handler for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling event handler for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("enable_event_handler", false);
+}
+
+void ExternalCommandProcessor::EnableSvcEventHandler(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable event handler for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling event handler for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("enable_event_handler", true);
+}
+
+void ExternalCommandProcessor::DisableSvcEventHandler(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable event handler for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling event handler for service '" << arguments[1] + "'";
+
+ service->ModifyAttribute("enable_event_handler", false);
+}
+
+void ExternalCommandProcessor::ChangeHostEventHandler(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change event handler for non-existent host '" + arguments[0] + "'"));
+
+ if (arguments[1].IsEmpty()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Unsetting event handler for host '" << arguments[0] << "'";
+
+ host->ModifyAttribute("event_command", "");
+ } else {
+ EventCommand::Ptr command = EventCommand::GetByName(arguments[1]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Event command '" + arguments[1] + "' does not exist."));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing event handler for host '" << arguments[0] << "' to '" << arguments[1] << "'";
+
+ host->ModifyAttribute("event_command", command->GetName());
+ }
+}
+
+void ExternalCommandProcessor::ChangeSvcEventHandler(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change event handler for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ if (arguments[2].IsEmpty()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Unsetting event handler for service '" << arguments[1] << "'";
+
+ service->ModifyAttribute("event_command", "");
+ } else {
+ EventCommand::Ptr command = EventCommand::GetByName(arguments[2]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Event command '" + arguments[2] + "' does not exist."));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing event handler for service '" << arguments[1] << "' to '" << arguments[2] << "'";
+
+ service->ModifyAttribute("event_command", command->GetName());
+ }
+}
+
+void ExternalCommandProcessor::ChangeHostCheckCommand(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change check command for non-existent host '" + arguments[0] + "'"));
+
+ CheckCommand::Ptr command = CheckCommand::GetByName(arguments[1]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Check command '" + arguments[1] + "' does not exist."));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing check command for host '" << arguments[0] << "' to '" << arguments[1] << "'";
+
+ host->ModifyAttribute("check_command", command->GetName());
+}
+
+void ExternalCommandProcessor::ChangeSvcCheckCommand(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change check command for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ CheckCommand::Ptr command = CheckCommand::GetByName(arguments[2]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Check command '" + arguments[2] + "' does not exist."));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing check command for service '" << arguments[1] << "' to '" << arguments[2] << "'";
+
+ service->ModifyAttribute("check_command", command->GetName());
+}
+
+void ExternalCommandProcessor::ChangeMaxHostCheckAttempts(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change max check attempts for non-existent host '" + arguments[0] + "'"));
+
+ int attempts = Convert::ToLong(arguments[1]);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing max check attempts for host '" << arguments[0] << "' to '" << arguments[1] << "'";
+
+ host->ModifyAttribute("max_check_attempts", attempts);
+}
+
+void ExternalCommandProcessor::ChangeMaxSvcCheckAttempts(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change max check attempts for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ int attempts = Convert::ToLong(arguments[2]);
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing max check attempts for service '" << arguments[1] << "' to '" << arguments[2] << "'";
+
+ service->ModifyAttribute("max_check_attempts", attempts);
+}
+
+void ExternalCommandProcessor::ChangeHostCheckTimeperiod(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change check period for non-existent host '" + arguments[0] + "'"));
+
+ TimePeriod::Ptr tp = TimePeriod::GetByName(arguments[1]);
+
+ if (!tp)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Time period '" + arguments[1] + "' does not exist."));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing check period for host '" << arguments[0] << "' to '" << arguments[1] << "'";
+
+ host->ModifyAttribute("check_period", tp->GetName());
+}
+
+void ExternalCommandProcessor::ChangeSvcCheckTimeperiod(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change check period for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ TimePeriod::Ptr tp = TimePeriod::GetByName(arguments[2]);
+
+ if (!tp)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Time period '" + arguments[2] + "' does not exist."));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing check period for service '" << arguments[1] << "' to '" << arguments[2] << "'";
+
+ service->ModifyAttribute("check_period", tp->GetName());
+}
+
+void ExternalCommandProcessor::ChangeCustomHostVar(double, const std::vector<String>& arguments)
+{
+ Host::Ptr host = Host::GetByName(arguments[0]);
+
+ if (!host)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change custom var for non-existent host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing custom var '" << arguments[1] << "' for host '" << arguments[0] << "' to value '" << arguments[2] << "'";
+
+ host->ModifyAttribute("vars." + arguments[1], arguments[2]);
+}
+
+void ExternalCommandProcessor::ChangeCustomSvcVar(double, const std::vector<String>& arguments)
+{
+ Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]);
+
+ if (!service)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change custom var for non-existent service '" + arguments[1] + "' on host '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing custom var '" << arguments[2] << "' for service '" << arguments[1] << "' on host '"
+ << arguments[0] << "' to value '" << arguments[3] << "'";
+
+ service->ModifyAttribute("vars." + arguments[2], arguments[3]);
+}
+
+void ExternalCommandProcessor::ChangeCustomUserVar(double, const std::vector<String>& arguments)
+{
+ User::Ptr user = User::GetByName(arguments[0]);
+
+ if (!user)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change custom var for non-existent user '" + arguments[0] + "'"));
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing custom var '" << arguments[1] << "' for user '" << arguments[0] << "' to value '" << arguments[2] << "'";
+
+ user->ModifyAttribute("vars." + arguments[1], arguments[2]);
+}
+
+void ExternalCommandProcessor::ChangeCustomCheckcommandVar(double, const std::vector<String>& arguments)
+{
+ CheckCommand::Ptr command = CheckCommand::GetByName(arguments[0]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change custom var for non-existent command '" + arguments[0] + "'"));
+
+ ChangeCustomCommandVarInternal(command, arguments[1], arguments[2]);
+}
+
+void ExternalCommandProcessor::ChangeCustomEventcommandVar(double, const std::vector<String>& arguments)
+{
+ EventCommand::Ptr command = EventCommand::GetByName(arguments[0]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change custom var for non-existent command '" + arguments[0] + "'"));
+
+ ChangeCustomCommandVarInternal(command, arguments[1], arguments[2]);
+}
+
+void ExternalCommandProcessor::ChangeCustomNotificationcommandVar(double, const std::vector<String>& arguments)
+{
+ NotificationCommand::Ptr command = NotificationCommand::GetByName(arguments[0]);
+
+ if (!command)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot change custom var for non-existent command '" + arguments[0] + "'"));
+
+ ChangeCustomCommandVarInternal(command, arguments[1], arguments[2]);
+}
+
+void ExternalCommandProcessor::ChangeCustomCommandVarInternal(const Command::Ptr& command, const String& name, const Value& value)
+{
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Changing custom var '" << name << "' for command '" << command->GetName() << "' to value '" << value << "'";
+
+ command->ModifyAttribute("vars." + name, value);
+}
+
+void ExternalCommandProcessor::EnableHostgroupHostNotifications(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable host notifications for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_notifications", true);
+ }
+}
+
+void ExternalCommandProcessor::EnableHostgroupSvcNotifications(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable service notifications for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_notifications", true);
+ }
+ }
+}
+
+void ExternalCommandProcessor::DisableHostgroupHostNotifications(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable host notifications for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_notifications", false);
+ }
+}
+
+void ExternalCommandProcessor::DisableHostgroupSvcNotifications(double, const std::vector<String>& arguments)
+{
+ HostGroup::Ptr hg = HostGroup::GetByName(arguments[0]);
+
+ if (!hg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable service notifications for non-existent hostgroup '" + arguments[0] + "'"));
+
+ for (const Host::Ptr& host : hg->GetMembers()) {
+ for (const Service::Ptr& service : host->GetServices()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_notifications", false);
+ }
+ }
+}
+
+void ExternalCommandProcessor::EnableServicegroupHostNotifications(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable host notifications for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_notifications", true);
+ }
+}
+
+void ExternalCommandProcessor::EnableServicegroupSvcNotifications(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot enable service notifications for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Enabling notifications for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_notifications", true);
+ }
+}
+
+void ExternalCommandProcessor::DisableServicegroupHostNotifications(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable host notifications for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Host::Ptr host = service->GetHost();
+
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for host '" << host->GetName() << "'";
+
+ host->ModifyAttribute("enable_notifications", false);
+ }
+}
+
+void ExternalCommandProcessor::DisableServicegroupSvcNotifications(double, const std::vector<String>& arguments)
+{
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(arguments[0]);
+
+ if (!sg)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot disable service notifications for non-existent servicegroup '" + arguments[0] + "'"));
+
+ for (const Service::Ptr& service : sg->GetMembers()) {
+ Log(LogNotice, "ExternalCommandProcessor")
+ << "Disabling notifications for service '" << service->GetName() << "'";
+
+ service->ModifyAttribute("enable_notifications", false);
+ }
+}
+
+std::mutex& ExternalCommandProcessor::GetMutex()
+{
+ static std::mutex mtx;
+ return mtx;
+}
+
+std::map<String, ExternalCommandInfo>& ExternalCommandProcessor::GetCommands()
+{
+ static std::map<String, ExternalCommandInfo> commands;
+ return commands;
+}
+
diff --git a/lib/icinga/externalcommandprocessor.hpp b/lib/icinga/externalcommandprocessor.hpp
new file mode 100644
index 0000000..a7c5a30
--- /dev/null
+++ b/lib/icinga/externalcommandprocessor.hpp
@@ -0,0 +1,169 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef EXTERNALCOMMANDPROCESSOR_H
+#define EXTERNALCOMMANDPROCESSOR_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/command.hpp"
+#include "base/string.hpp"
+#include <boost/signals2.hpp>
+#include <vector>
+
+namespace icinga
+{
+
+typedef std::function<void (double, const std::vector<String>& arguments)> ExternalCommandCallback;
+
+struct ExternalCommandInfo
+{
+ ExternalCommandCallback Callback;
+ size_t MinArgs;
+ size_t MaxArgs;
+};
+
+class ExternalCommandProcessor {
+public:
+ static void Execute(const String& line);
+ static void Execute(double time, const String& command, const std::vector<String>& arguments);
+
+ static boost::signals2::signal<void(double, const String&, const std::vector<String>&)> OnNewExternalCommand;
+
+private:
+ ExternalCommandProcessor();
+
+ static void ExecuteFromFile(const String& line, std::deque< std::vector<String> >& file_queue);
+
+ static void ProcessHostCheckResult(double time, const std::vector<String>& arguments);
+ static void ProcessServiceCheckResult(double time, const std::vector<String>& arguments);
+ static void ScheduleHostCheck(double time, const std::vector<String>& arguments);
+ static void ScheduleForcedHostCheck(double time, const std::vector<String>& arguments);
+ static void ScheduleSvcCheck(double time, const std::vector<String>& arguments);
+ static void ScheduleForcedSvcCheck(double time, const std::vector<String>& arguments);
+ static void EnableHostCheck(double time, const std::vector<String>& arguments);
+ static void DisableHostCheck(double time, const std::vector<String>& arguments);
+ static void EnableSvcCheck(double time, const std::vector<String>& arguments);
+ static void DisableSvcCheck(double time, const std::vector<String>& arguments);
+ static void ShutdownProcess(double time, const std::vector<String>& arguments);
+ static void RestartProcess(double time, const std::vector<String>& arguments);
+ static void ScheduleForcedHostSvcChecks(double time, const std::vector<String>& arguments);
+ static void ScheduleHostSvcChecks(double time, const std::vector<String>& arguments);
+ static void EnableHostSvcChecks(double time, const std::vector<String>& arguments);
+ static void DisableHostSvcChecks(double time, const std::vector<String>& arguments);
+ static void AcknowledgeSvcProblem(double time, const std::vector<String>& arguments);
+ static void AcknowledgeSvcProblemExpire(double time, const std::vector<String>& arguments);
+ static void RemoveSvcAcknowledgement(double time, const std::vector<String>& arguments);
+ static void AcknowledgeHostProblem(double time, const std::vector<String>& arguments);
+ static void AcknowledgeHostProblemExpire(double time, const std::vector<String>& arguments);
+ static void RemoveHostAcknowledgement(double time, const std::vector<String>& arguments);
+ static void EnableHostgroupSvcChecks(double time, const std::vector<String>& arguments);
+ static void DisableHostgroupSvcChecks(double time, const std::vector<String>& arguments);
+ static void EnableServicegroupSvcChecks(double time, const std::vector<String>& arguments);
+ static void DisableServicegroupSvcChecks(double time, const std::vector<String>& arguments);
+ static void EnablePassiveHostChecks(double time, const std::vector<String>& arguments);
+ static void DisablePassiveHostChecks(double time, const std::vector<String>& arguments);
+ static void EnablePassiveSvcChecks(double time, const std::vector<String>& arguments);
+ static void DisablePassiveSvcChecks(double time, const std::vector<String>& arguments);
+ static void EnableServicegroupPassiveSvcChecks(double time, const std::vector<String>& arguments);
+ static void DisableServicegroupPassiveSvcChecks(double time, const std::vector<String>& arguments);
+ static void EnableHostgroupPassiveSvcChecks(double time, const std::vector<String>& arguments);
+ static void DisableHostgroupPassiveSvcChecks(double time, const std::vector<String>& arguments);
+ static void ProcessFile(double time, const std::vector<String>& arguments);
+ static void ScheduleSvcDowntime(double time, const std::vector<String>& arguments);
+ static void DelSvcDowntime(double time, const std::vector<String>& arguments);
+ static void ScheduleHostDowntime(double time, const std::vector<String>& arguments);
+ static void ScheduleAndPropagateHostDowntime(double, const std::vector<String>& arguments);
+ static void ScheduleAndPropagateTriggeredHostDowntime(double, const std::vector<String>& arguments);
+ static void DelHostDowntime(double time, const std::vector<String>& arguments);
+ static void DelDowntimeByHostName(double, const std::vector<String>& arguments);
+ static void ScheduleHostSvcDowntime(double time, const std::vector<String>& arguments);
+ static void ScheduleHostgroupHostDowntime(double time, const std::vector<String>& arguments);
+ static void ScheduleHostgroupSvcDowntime(double time, const std::vector<String>& arguments);
+ static void ScheduleServicegroupHostDowntime(double time, const std::vector<String>& arguments);
+ static void ScheduleServicegroupSvcDowntime(double time, const std::vector<String>& arguments);
+ static void AddHostComment(double time, const std::vector<String>& arguments);
+ static void DelHostComment(double time, const std::vector<String>& arguments);
+ static void AddSvcComment(double time, const std::vector<String>& arguments);
+ static void DelSvcComment(double time, const std::vector<String>& arguments);
+ static void DelAllHostComments(double time, const std::vector<String>& arguments);
+ static void DelAllSvcComments(double time, const std::vector<String>& arguments);
+ static void SendCustomHostNotification(double time, const std::vector<String>& arguments);
+ static void SendCustomSvcNotification(double time, const std::vector<String>& arguments);
+ static void DelayHostNotification(double time, const std::vector<String>& arguments);
+ static void DelaySvcNotification(double time, const std::vector<String>& arguments);
+ static void EnableHostNotifications(double time, const std::vector<String>& arguments);
+ static void DisableHostNotifications(double time, const std::vector<String>& arguments);
+ static void EnableSvcNotifications(double time, const std::vector<String>& arguments);
+ static void DisableSvcNotifications(double time, const std::vector<String>& arguments);
+ static void EnableHostSvcNotifications(double, const std::vector<String>& arguments);
+ static void DisableHostSvcNotifications(double, const std::vector<String>& arguments);
+ static void DisableHostgroupHostChecks(double, const std::vector<String>& arguments);
+ static void DisableHostgroupPassiveHostChecks(double, const std::vector<String>& arguments);
+ static void DisableServicegroupHostChecks(double, const std::vector<String>& arguments);
+ static void DisableServicegroupPassiveHostChecks(double, const std::vector<String>& arguments);
+ static void EnableHostgroupHostChecks(double, const std::vector<String>& arguments);
+ static void EnableHostgroupPassiveHostChecks(double, const std::vector<String>& arguments);
+ static void EnableServicegroupHostChecks(double, const std::vector<String>& arguments);
+ static void EnableServicegroupPassiveHostChecks(double, const std::vector<String>& arguments);
+ static void EnableSvcFlapping(double time, const std::vector<String>& arguments);
+ static void DisableSvcFlapping(double time, const std::vector<String>& arguments);
+ static void EnableHostFlapping(double time, const std::vector<String>& arguments);
+ static void DisableHostFlapping(double time, const std::vector<String>& arguments);
+ static void EnableNotifications(double time, const std::vector<String>& arguments);
+ static void DisableNotifications(double time, const std::vector<String>& arguments);
+ static void EnableFlapDetection(double time, const std::vector<String>& arguments);
+ static void DisableFlapDetection(double time, const std::vector<String>& arguments);
+ static void EnableEventHandlers(double time, const std::vector<String>& arguments);
+ static void DisableEventHandlers(double time, const std::vector<String>& arguments);
+ static void EnablePerformanceData(double time, const std::vector<String>& arguments);
+ static void DisablePerformanceData(double time, const std::vector<String>& arguments);
+ static void StartExecutingSvcChecks(double time, const std::vector<String>& arguments);
+ static void StopExecutingSvcChecks(double time, const std::vector<String>& arguments);
+ static void StartExecutingHostChecks(double time, const std::vector<String>& arguments);
+ static void StopExecutingHostChecks(double time, const std::vector<String>& arguments);
+
+ static void ChangeNormalSvcCheckInterval(double time, const std::vector<String>& arguments);
+ static void ChangeNormalHostCheckInterval(double time, const std::vector<String>& arguments);
+ static void ChangeRetrySvcCheckInterval(double time, const std::vector<String>& arguments);
+ static void ChangeRetryHostCheckInterval(double time, const std::vector<String>& arguments);
+ static void EnableHostEventHandler(double time, const std::vector<String>& arguments);
+ static void DisableHostEventHandler(double time, const std::vector<String>& arguments);
+ static void EnableSvcEventHandler(double time, const std::vector<String>& arguments);
+ static void DisableSvcEventHandler(double time, const std::vector<String>& arguments);
+ static void ChangeHostEventHandler(double time, const std::vector<String>& arguments);
+ static void ChangeSvcEventHandler(double time, const std::vector<String>& arguments);
+ static void ChangeHostCheckCommand(double time, const std::vector<String>& arguments);
+ static void ChangeSvcCheckCommand(double time, const std::vector<String>& arguments);
+ static void ChangeMaxHostCheckAttempts(double time, const std::vector<String>& arguments);
+ static void ChangeMaxSvcCheckAttempts(double time, const std::vector<String>& arguments);
+ static void ChangeHostCheckTimeperiod(double time, const std::vector<String>& arguments);
+ static void ChangeSvcCheckTimeperiod(double time, const std::vector<String>& arguments);
+ static void ChangeCustomHostVar(double time, const std::vector<String>& arguments);
+ static void ChangeCustomSvcVar(double time, const std::vector<String>& arguments);
+ static void ChangeCustomUserVar(double time, const std::vector<String>& arguments);
+ static void ChangeCustomCheckcommandVar(double time, const std::vector<String>& arguments);
+ static void ChangeCustomEventcommandVar(double time, const std::vector<String>& arguments);
+ static void ChangeCustomNotificationcommandVar(double time, const std::vector<String>& arguments);
+
+ static void EnableHostgroupHostNotifications(double time, const std::vector<String>& arguments);
+ static void EnableHostgroupSvcNotifications(double time, const std::vector<String>& arguments);
+ static void DisableHostgroupHostNotifications(double time, const std::vector<String>& arguments);
+ static void DisableHostgroupSvcNotifications(double time, const std::vector<String>& arguments);
+ static void EnableServicegroupHostNotifications(double time, const std::vector<String>& arguments);
+ static void EnableServicegroupSvcNotifications(double time, const std::vector<String>& arguments);
+ static void DisableServicegroupHostNotifications(double time, const std::vector<String>& arguments);
+ static void DisableServicegroupSvcNotifications(double time, const std::vector<String>& arguments);
+
+private:
+ static void ChangeCustomCommandVarInternal(const Command::Ptr& command, const String& name, const Value& value);
+
+ static void RegisterCommand(const String& command, const ExternalCommandCallback& callback, size_t minArgs = 0, size_t maxArgs = UINT_MAX);
+ static void RegisterCommands();
+
+ static std::mutex& GetMutex();
+ static std::map<String, ExternalCommandInfo>& GetCommands();
+
+};
+
+}
+
+#endif /* EXTERNALCOMMANDPROCESSOR_H */
diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp
new file mode 100644
index 0000000..36149d3
--- /dev/null
+++ b/lib/icinga/host.cpp
@@ -0,0 +1,330 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/host.hpp"
+#include "icinga/host-ti.cpp"
+#include "icinga/service.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/pluginutility.hpp"
+#include "icinga/scheduleddowntime.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+#include "base/debug.hpp"
+#include "base/json.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(Host);
+
+void Host::OnAllConfigLoaded()
+{
+ ObjectImpl<Host>::OnAllConfigLoaded();
+
+ String zoneName = GetZoneName();
+
+ if (!zoneName.IsEmpty()) {
+ Zone::Ptr zone = Zone::GetByName(zoneName);
+
+ if (zone && zone->IsGlobal())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Host '" + GetName() + "' cannot be put into global zone '" + zone->GetName() + "'."));
+ }
+
+ HostGroup::EvaluateObjectRules(this);
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups) {
+ groups = groups->ShallowClone();
+
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ HostGroup::Ptr hg = HostGroup::GetByName(name);
+
+ if (hg)
+ hg->ResolveGroupMembership(this, true);
+ }
+ }
+}
+
+void Host::CreateChildObjects(const Type::Ptr& childType)
+{
+ if (childType == ScheduledDowntime::TypeInstance)
+ ScheduledDowntime::EvaluateApplyRules(this);
+
+ if (childType == Notification::TypeInstance)
+ Notification::EvaluateApplyRules(this);
+
+ if (childType == Dependency::TypeInstance)
+ Dependency::EvaluateApplyRules(this);
+
+ if (childType == Service::TypeInstance)
+ Service::EvaluateApplyRules(this);
+}
+
+void Host::Stop(bool runtimeRemoved)
+{
+ ObjectImpl<Host>::Stop(runtimeRemoved);
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups) {
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ HostGroup::Ptr hg = HostGroup::GetByName(name);
+
+ if (hg)
+ hg->ResolveGroupMembership(this, false);
+ }
+ }
+
+ // TODO: unregister slave services/notifications?
+}
+
+std::vector<Service::Ptr> Host::GetServices() const
+{
+ std::unique_lock<std::mutex> lock(m_ServicesMutex);
+
+ std::vector<Service::Ptr> services;
+ services.reserve(m_Services.size());
+ typedef std::pair<String, Service::Ptr> ServicePair;
+ for (const ServicePair& kv : m_Services) {
+ services.push_back(kv.second);
+ }
+
+ return services;
+}
+
+void Host::AddService(const Service::Ptr& service)
+{
+ std::unique_lock<std::mutex> lock(m_ServicesMutex);
+
+ m_Services[service->GetShortName()] = service;
+}
+
+void Host::RemoveService(const Service::Ptr& service)
+{
+ std::unique_lock<std::mutex> lock(m_ServicesMutex);
+
+ m_Services.erase(service->GetShortName());
+}
+
+int Host::GetTotalServices() const
+{
+ return GetServices().size();
+}
+
+Service::Ptr Host::GetServiceByShortName(const Value& name)
+{
+ if (name.IsScalar()) {
+ {
+ std::unique_lock<std::mutex> lock(m_ServicesMutex);
+
+ auto it = m_Services.find(name);
+
+ if (it != m_Services.end())
+ return it->second;
+ }
+
+ return nullptr;
+ } else if (name.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr dict = name;
+ String short_name;
+
+ return Service::GetByNamePair(dict->Get("host"), dict->Get("service"));
+ } else {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Host/Service name pair is invalid: " + JsonEncode(name)));
+ }
+}
+
+HostState Host::CalculateState(ServiceState state)
+{
+ switch (state) {
+ case ServiceOK:
+ case ServiceWarning:
+ return HostUp;
+ default:
+ return HostDown;
+ }
+}
+
+HostState Host::GetState() const
+{
+ return CalculateState(GetStateRaw());
+}
+
+HostState Host::GetLastState() const
+{
+ return CalculateState(GetLastStateRaw());
+}
+
+HostState Host::GetLastHardState() const
+{
+ return CalculateState(GetLastHardStateRaw());
+}
+
+/* keep in sync with Service::GetSeverity()
+ * One could think it may be smart to use an enum and some bitmask math here.
+ * But the only thing the consuming icingaweb2 cares about is being able to
+ * sort by severity. It is therefore easier to keep them seperated here. */
+int Host::GetSeverity() const
+{
+ int severity = 0;
+
+ ObjectLock olock(this);
+ HostState state = GetState();
+
+ if (!HasBeenChecked()) {
+ severity = 16;
+ } else if (state == HostUp) {
+ severity = 0;
+ } else {
+ if (IsReachable())
+ severity = 64;
+ else
+ severity = 32;
+
+ if (IsAcknowledged())
+ severity += 512;
+ else if (IsInDowntime())
+ severity += 256;
+ else
+ severity += 2048;
+ }
+
+ olock.Unlock();
+
+ return severity;
+
+}
+
+bool Host::IsStateOK(ServiceState state) const
+{
+ return Host::CalculateState(state) == HostUp;
+}
+
+void Host::SaveLastState(ServiceState state, double timestamp)
+{
+ if (state == ServiceOK || state == ServiceWarning)
+ SetLastStateUp(timestamp);
+ else if (state == ServiceCritical)
+ SetLastStateDown(timestamp);
+}
+
+HostState Host::StateFromString(const String& state)
+{
+ if (state == "UP")
+ return HostUp;
+ else
+ return HostDown;
+}
+
+String Host::StateToString(HostState state)
+{
+ switch (state) {
+ case HostUp:
+ return "UP";
+ case HostDown:
+ return "DOWN";
+ default:
+ return "INVALID";
+ }
+}
+
+StateType Host::StateTypeFromString(const String& type)
+{
+ if (type == "SOFT")
+ return StateTypeSoft;
+ else
+ return StateTypeHard;
+}
+
+String Host::StateTypeToString(StateType type)
+{
+ if (type == StateTypeSoft)
+ return "SOFT";
+ else
+ return "HARD";
+}
+
+bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
+{
+ if (macro == "state") {
+ *result = StateToString(GetState());
+ return true;
+ } else if (macro == "state_id") {
+ *result = GetState();
+ return true;
+ } else if (macro == "state_type") {
+ *result = StateTypeToString(GetStateType());
+ return true;
+ } else if (macro == "last_state") {
+ *result = StateToString(GetLastState());
+ return true;
+ } else if (macro == "last_state_id") {
+ *result = GetLastState();
+ return true;
+ } else if (macro == "last_state_type") {
+ *result = StateTypeToString(GetLastStateType());
+ return true;
+ } else if (macro == "last_state_change") {
+ *result = static_cast<long>(GetLastStateChange());
+ return true;
+ } else if (macro == "downtime_depth") {
+ *result = GetDowntimeDepth();
+ return true;
+ } else if (macro == "duration_sec") {
+ *result = Utility::GetTime() - GetLastStateChange();
+ return true;
+ } else if (macro == "num_services" || macro == "num_services_ok" || macro == "num_services_warning"
+ || macro == "num_services_unknown" || macro == "num_services_critical") {
+ int filter = -1;
+ int count = 0;
+
+ if (macro == "num_services_ok")
+ filter = ServiceOK;
+ else if (macro == "num_services_warning")
+ filter = ServiceWarning;
+ else if (macro == "num_services_unknown")
+ filter = ServiceUnknown;
+ else if (macro == "num_services_critical")
+ filter = ServiceCritical;
+
+ for (const Service::Ptr& service : GetServices()) {
+ if (filter != -1 && service->GetState() != filter)
+ continue;
+
+ count++;
+ }
+
+ *result = count;
+ return true;
+ }
+
+ CheckResult::Ptr cr = GetLastCheckResult();
+
+ if (cr) {
+ if (macro == "latency") {
+ *result = cr->CalculateLatency();
+ return true;
+ } else if (macro == "execution_time") {
+ *result = cr->CalculateExecutionTime();
+ return true;
+ } else if (macro == "output") {
+ *result = cr->GetOutput();
+ return true;
+ } else if (macro == "perfdata") {
+ *result = PluginUtility::FormatPerfdata(cr->GetPerformanceData());
+ return true;
+ } else if (macro == "check_source") {
+ *result = cr->GetCheckSource();
+ return true;
+ } else if (macro == "scheduling_source") {
+ *result = cr->GetSchedulingSource();
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/lib/icinga/host.hpp b/lib/icinga/host.hpp
new file mode 100644
index 0000000..d0d6c1a
--- /dev/null
+++ b/lib/icinga/host.hpp
@@ -0,0 +1,71 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef HOST_H
+#define HOST_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/host-ti.hpp"
+#include "icinga/macroresolver.hpp"
+#include "icinga/checkresult.hpp"
+
+namespace icinga
+{
+
+class Service;
+
+/**
+ * An Icinga host.
+ *
+ * @ingroup icinga
+ */
+class Host final : public ObjectImpl<Host>, public MacroResolver
+{
+public:
+ DECLARE_OBJECT(Host);
+ DECLARE_OBJECTNAME(Host);
+
+ intrusive_ptr<Service> GetServiceByShortName(const Value& name);
+
+ std::vector<intrusive_ptr<Service> > GetServices() const;
+ void AddService(const intrusive_ptr<Service>& service);
+ void RemoveService(const intrusive_ptr<Service>& service);
+
+ int GetTotalServices() const;
+
+ static HostState CalculateState(ServiceState state);
+
+ HostState GetState() const override;
+ HostState GetLastState() const override;
+ HostState GetLastHardState() const override;
+ int GetSeverity() const override;
+
+ bool IsStateOK(ServiceState state) const override;
+ void SaveLastState(ServiceState state, double timestamp) override;
+
+ static HostState StateFromString(const String& state);
+ static String StateToString(HostState state);
+
+ static StateType StateTypeFromString(const String& state);
+ static String StateTypeToString(StateType state);
+
+ bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const override;
+
+ void OnAllConfigLoaded() override;
+
+protected:
+ void Stop(bool runtimeRemoved) override;
+
+ void CreateChildObjects(const Type::Ptr& childType) override;
+
+private:
+ mutable std::mutex m_ServicesMutex;
+ std::map<String, intrusive_ptr<Service> > m_Services;
+
+ static void RefreshServicesCache();
+};
+
+}
+
+#endif /* HOST_H */
+
+#include "icinga/service.hpp"
diff --git a/lib/icinga/host.ti b/lib/icinga/host.ti
new file mode 100644
index 0000000..f6624e3
--- /dev/null
+++ b/lib/icinga/host.ti
@@ -0,0 +1,48 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/customvarobject.hpp"
+#impl_include "icinga/hostgroup.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class Host : Checkable
+{
+ load_after ApiListener;
+ load_after Endpoint;
+ load_after Zone;
+
+ [config, no_user_modify, required, signal_with_old_value] array(name(HostGroup)) groups {
+ default {{{ return new Array(); }}}
+ };
+
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetName();
+ else
+ return displayName;
+ }}}
+ };
+
+ [config] String address;
+ [config] String address6;
+
+ [enum, no_storage] HostState "state" {
+ get;
+ };
+ [enum, no_storage] HostState last_state {
+ get;
+ };
+ [enum, no_storage] HostState last_hard_state {
+ get;
+ };
+ [state] Timestamp last_state_up;
+ [state] Timestamp last_state_down;
+};
+
+}
diff --git a/lib/icinga/hostgroup.cpp b/lib/icinga/hostgroup.cpp
new file mode 100644
index 0000000..a22f3b7
--- /dev/null
+++ b/lib/icinga/hostgroup.cpp
@@ -0,0 +1,108 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/hostgroup.hpp"
+#include "icinga/hostgroup-ti.cpp"
+#include "config/objectrule.hpp"
+#include "config/configitem.hpp"
+#include "base/configtype.hpp"
+#include "base/logger.hpp"
+#include "base/objectlock.hpp"
+#include "base/context.hpp"
+#include "base/workqueue.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(HostGroup);
+
+INITIALIZE_ONCE([]() {
+ ObjectRule::RegisterType("HostGroup");
+});
+
+bool HostGroup::EvaluateObjectRule(const Host::Ptr& host, const ConfigItem::Ptr& group)
+{
+ String groupName = group->GetName();
+
+ CONTEXT("Evaluating rule for group '" << groupName << "'");
+
+ ScriptFrame frame(true);
+ if (group->GetScope())
+ group->GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("host", host);
+
+ if (!group->GetFilter()->Evaluate(frame).GetValue().ToBool())
+ return false;
+
+ Log(LogDebug, "HostGroup")
+ << "Assigning membership for group '" << groupName << "' to host '" << host->GetName() << "'";
+
+ Array::Ptr groups = host->GetGroups();
+
+ if (groups && !groups->Contains(groupName))
+ groups->Add(groupName);
+
+ return true;
+}
+
+void HostGroup::EvaluateObjectRules(const Host::Ptr& host)
+{
+ CONTEXT("Evaluating group memberships for host '" << host->GetName() << "'");
+
+ for (const ConfigItem::Ptr& group : ConfigItem::GetItems(HostGroup::TypeInstance))
+ {
+ if (!group->GetFilter())
+ continue;
+
+ EvaluateObjectRule(host, group);
+ }
+}
+
+std::set<Host::Ptr> HostGroup::GetMembers() const
+{
+ std::unique_lock<std::mutex> lock(m_HostGroupMutex);
+ return m_Members;
+}
+
+void HostGroup::AddMember(const Host::Ptr& host)
+{
+ host->AddGroup(GetName());
+
+ std::unique_lock<std::mutex> lock(m_HostGroupMutex);
+ m_Members.insert(host);
+}
+
+void HostGroup::RemoveMember(const Host::Ptr& host)
+{
+ std::unique_lock<std::mutex> lock(m_HostGroupMutex);
+ m_Members.erase(host);
+}
+
+bool HostGroup::ResolveGroupMembership(const Host::Ptr& host, bool add, int rstack) {
+
+ if (add && rstack > 20) {
+ Log(LogWarning, "HostGroup")
+ << "Too many nested groups for group '" << GetName() << "': Host '"
+ << host->GetName() << "' membership assignment failed.";
+
+ return false;
+ }
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups && groups->GetLength() > 0) {
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ HostGroup::Ptr group = HostGroup::GetByName(name);
+
+ if (group && !group->ResolveGroupMembership(host, add, rstack + 1))
+ return false;
+ }
+ }
+
+ if (add)
+ AddMember(host);
+ else
+ RemoveMember(host);
+
+ return true;
+}
diff --git a/lib/icinga/hostgroup.hpp b/lib/icinga/hostgroup.hpp
new file mode 100644
index 0000000..3ad5d26
--- /dev/null
+++ b/lib/icinga/hostgroup.hpp
@@ -0,0 +1,43 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef HOSTGROUP_H
+#define HOSTGROUP_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/hostgroup-ti.hpp"
+#include "icinga/host.hpp"
+
+namespace icinga
+{
+
+class ConfigItem;
+
+/**
+ * An Icinga host group.
+ *
+ * @ingroup icinga
+ */
+class HostGroup final : public ObjectImpl<HostGroup>
+{
+public:
+ DECLARE_OBJECT(HostGroup);
+ DECLARE_OBJECTNAME(HostGroup);
+
+ std::set<Host::Ptr> GetMembers() const;
+ void AddMember(const Host::Ptr& host);
+ void RemoveMember(const Host::Ptr& host);
+
+ bool ResolveGroupMembership(const Host::Ptr& host, bool add = true, int rstack = 0);
+
+ static void EvaluateObjectRules(const Host::Ptr& host);
+
+private:
+ mutable std::mutex m_HostGroupMutex;
+ std::set<Host::Ptr> m_Members;
+
+ static bool EvaluateObjectRule(const Host::Ptr& host, const intrusive_ptr<ConfigItem>& item);
+};
+
+}
+
+#endif /* HOSTGROUP_H */
diff --git a/lib/icinga/hostgroup.ti b/lib/icinga/hostgroup.ti
new file mode 100644
index 0000000..b679344
--- /dev/null
+++ b/lib/icinga/hostgroup.ti
@@ -0,0 +1,28 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class HostGroup : CustomVarObject
+{
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetName();
+ else
+ return displayName;
+ }}}
+ };
+
+ [config, no_user_modify] array(name(HostGroup)) groups;
+ [config] String notes;
+ [config] String notes_url;
+ [config] String action_url;
+};
+
+}
diff --git a/lib/icinga/i2-icinga.hpp b/lib/icinga/i2-icinga.hpp
new file mode 100644
index 0000000..7163822
--- /dev/null
+++ b/lib/icinga/i2-icinga.hpp
@@ -0,0 +1,15 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef I2ICINGA_H
+#define I2ICINGA_H
+
+/**
+ * @defgroup icinga Icinga library
+ *
+ * The Icinga library implements all Icinga-specific functionality that is
+ * common to all components (e.g. hosts, services, etc.).
+ */
+
+#include "base/i2-base.hpp"
+
+#endif /* I2ICINGA_H */
diff --git a/lib/icinga/icinga-itl.conf b/lib/icinga/icinga-itl.conf
new file mode 100644
index 0000000..22b688a
--- /dev/null
+++ b/lib/icinga/icinga-itl.conf
@@ -0,0 +1,15 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+System.assert(Internal.run_with_activation_context(function() {
+ template TimePeriod "legacy-timeperiod" use (LegacyTimePeriod = Internal.LegacyTimePeriod) default {
+ update = LegacyTimePeriod
+ }
+}))
+
+var methods = [
+ "LegacyTimePeriod"
+]
+
+for (method in methods) {
+ Internal.remove(method)
+}
diff --git a/lib/icinga/icingaapplication.cpp b/lib/icinga/icingaapplication.cpp
new file mode 100644
index 0000000..94ae0ed
--- /dev/null
+++ b/lib/icinga/icingaapplication.cpp
@@ -0,0 +1,321 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/icingaapplication.hpp"
+#include "icinga/icingaapplication-ti.cpp"
+#include "icinga/cib.hpp"
+#include "icinga/macroprocessor.hpp"
+#include "config/configcompiler.hpp"
+#include "base/atomic-file.hpp"
+#include "base/configwriter.hpp"
+#include "base/configtype.hpp"
+#include "base/exception.hpp"
+#include "base/logger.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include "base/debug.hpp"
+#include "base/utility.hpp"
+#include "base/timer.hpp"
+#include "base/scriptglobal.hpp"
+#include "base/initialize.hpp"
+#include "base/statsfunction.hpp"
+#include "base/loader.hpp"
+#include <fstream>
+
+using namespace icinga;
+
+static Timer::Ptr l_RetentionTimer;
+
+REGISTER_TYPE(IcingaApplication);
+/* Ensure that the priority is lower than the basic System namespace initialization in scriptframe.cpp. */
+INITIALIZE_ONCE_WITH_PRIORITY(&IcingaApplication::StaticInitialize, InitializePriority::InitIcingaApplication);
+
+static Namespace::Ptr l_IcingaNS;
+
+void IcingaApplication::StaticInitialize()
+{
+ /* Pre-fill global constants, can be overridden with user input later in icinga-app/icinga.cpp. */
+ String node_name = Utility::GetFQDN();
+
+ if (node_name.IsEmpty()) {
+ Log(LogNotice, "IcingaApplication", "No FQDN available. Trying Hostname.");
+ node_name = Utility::GetHostName();
+
+ if (node_name.IsEmpty()) {
+ Log(LogWarning, "IcingaApplication", "No FQDN nor Hostname available. Setting Nodename to 'localhost'.");
+ node_name = "localhost";
+ }
+ }
+
+ ScriptGlobal::Set("NodeName", node_name);
+
+ ScriptGlobal::Set("ReloadTimeout", 300);
+ ScriptGlobal::Set("MaxConcurrentChecks", 512);
+
+ Namespace::Ptr systemNS = ScriptGlobal::Get("System");
+ /* Ensure that the System namespace is already initialized. Otherwise this is a programming error. */
+ VERIFY(systemNS);
+
+ systemNS->Set("ApplicationType", "IcingaApplication", true);
+ systemNS->Set("ApplicationVersion", Application::GetAppVersion(), true);
+
+ Namespace::Ptr globalNS = ScriptGlobal::GetGlobals();
+ VERIFY(globalNS);
+
+ l_IcingaNS = new Namespace(true);
+ globalNS->Set("Icinga", l_IcingaNS, true);
+}
+
+INITIALIZE_ONCE_WITH_PRIORITY([]() {
+ l_IcingaNS->Freeze();
+}, InitializePriority::FreezeNamespaces);
+
+REGISTER_STATSFUNCTION(IcingaApplication, &IcingaApplication::StatsFunc);
+
+void IcingaApplication::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata)
+{
+ DictionaryData nodes;
+
+ for (const IcingaApplication::Ptr& icingaapplication : ConfigType::GetObjectsByType<IcingaApplication>()) {
+ nodes.emplace_back(icingaapplication->GetName(), new Dictionary({
+ { "node_name", icingaapplication->GetNodeName() },
+ { "enable_notifications", icingaapplication->GetEnableNotifications() },
+ { "enable_event_handlers", icingaapplication->GetEnableEventHandlers() },
+ { "enable_flapping", icingaapplication->GetEnableFlapping() },
+ { "enable_host_checks", icingaapplication->GetEnableHostChecks() },
+ { "enable_service_checks", icingaapplication->GetEnableServiceChecks() },
+ { "enable_perfdata", icingaapplication->GetEnablePerfdata() },
+ { "environment", icingaapplication->GetEnvironment() },
+ { "pid", Utility::GetPid() },
+ { "program_start", Application::GetStartTime() },
+ { "version", Application::GetAppVersion() }
+ }));
+ }
+
+ status->Set("icingaapplication", new Dictionary(std::move(nodes)));
+}
+
+/**
+ * The entry point for the Icinga application.
+ *
+ * @returns An exit status.
+ */
+int IcingaApplication::Main()
+{
+ Log(LogDebug, "IcingaApplication", "In IcingaApplication::Main()");
+
+ /* periodically dump the program state */
+ l_RetentionTimer = Timer::Create();
+ l_RetentionTimer->SetInterval(300);
+ l_RetentionTimer->OnTimerExpired.connect([this](const Timer * const&) { DumpProgramState(); });
+ l_RetentionTimer->Start();
+
+ RunEventLoop();
+
+ Log(LogInformation, "IcingaApplication", "Icinga has shut down.");
+
+ return EXIT_SUCCESS;
+}
+
+void IcingaApplication::OnShutdown()
+{
+ {
+ ObjectLock olock(this);
+ l_RetentionTimer->Stop();
+ }
+
+ DumpProgramState();
+}
+
+static void PersistModAttrHelper(AtomicFile& fp, ConfigObject::Ptr& previousObject, const ConfigObject::Ptr& object, const String& attr, const Value& value)
+{
+ if (object != previousObject) {
+ if (previousObject) {
+ ConfigWriter::EmitRaw(fp, "\tobj.version = ");
+ ConfigWriter::EmitValue(fp, 0, previousObject->GetVersion());
+ ConfigWriter::EmitRaw(fp, "\n}\n\n");
+ }
+
+ ConfigWriter::EmitRaw(fp, "var obj = ");
+
+ Array::Ptr args1 = new Array({
+ object->GetReflectionType()->GetName(),
+ object->GetName()
+ });
+ ConfigWriter::EmitFunctionCall(fp, "get_object", args1);
+
+ ConfigWriter::EmitRaw(fp, "\nif (obj) {\n");
+ }
+
+ ConfigWriter::EmitRaw(fp, "\tobj.");
+
+ Array::Ptr args2 = new Array({
+ attr,
+ value
+ });
+ ConfigWriter::EmitFunctionCall(fp, "modify_attribute", args2);
+
+ ConfigWriter::EmitRaw(fp, "\n");
+
+ previousObject = object;
+}
+
+void IcingaApplication::DumpProgramState()
+{
+ ConfigObject::DumpObjects(Configuration::StatePath);
+ DumpModifiedAttributes();
+}
+
+void IcingaApplication::DumpModifiedAttributes()
+{
+ String path = Configuration::ModAttrPath;
+
+ try {
+ Utility::Glob(path + ".tmp.*", &Utility::Remove, GlobFile);
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "IcingaApplication") << DiagnosticInformation(ex);
+ }
+
+ AtomicFile fp (path, 0644);
+
+ ConfigObject::Ptr previousObject;
+ ConfigObject::DumpModifiedAttributes([&fp, &previousObject](const ConfigObject::Ptr& object, const String& attr, const Value& value) {
+ PersistModAttrHelper(fp, previousObject, object, attr, value);
+ });
+
+ if (previousObject) {
+ ConfigWriter::EmitRaw(fp, "\tobj.version = ");
+ ConfigWriter::EmitValue(fp, 0, previousObject->GetVersion());
+ ConfigWriter::EmitRaw(fp, "\n}\n");
+ }
+
+ fp.Commit();
+}
+
+IcingaApplication::Ptr IcingaApplication::GetInstance()
+{
+ return static_pointer_cast<IcingaApplication>(Application::GetInstance());
+}
+
+bool IcingaApplication::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const
+{
+ double now = Utility::GetTime();
+
+ if (macro == "timet") {
+ *result = static_cast<long>(now);
+ return true;
+ } else if (macro == "long_date_time") {
+ *result = Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", now);
+ return true;
+ } else if (macro == "short_date_time") {
+ *result = Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", now);
+ return true;
+ } else if (macro == "date") {
+ *result = Utility::FormatDateTime("%Y-%m-%d", now);
+ return true;
+ } else if (macro == "time") {
+ *result = Utility::FormatDateTime("%H:%M:%S %z", now);
+ return true;
+ } else if (macro == "uptime") {
+ *result = Utility::FormatDuration(Application::GetUptime());
+ return true;
+ }
+
+ if (macro.Contains("num_services")) {
+ ServiceStatistics ss = CIB::CalculateServiceStats();
+
+ if (macro == "num_services_ok") {
+ *result = ss.services_ok;
+ return true;
+ } else if (macro == "num_services_warning") {
+ *result = ss.services_warning;
+ return true;
+ } else if (macro == "num_services_critical") {
+ *result = ss.services_critical;
+ return true;
+ } else if (macro == "num_services_unknown") {
+ *result = ss.services_unknown;
+ return true;
+ } else if (macro == "num_services_pending") {
+ *result = ss.services_pending;
+ return true;
+ } else if (macro == "num_services_unreachable") {
+ *result = ss.services_unreachable;
+ return true;
+ } else if (macro == "num_services_flapping") {
+ *result = ss.services_flapping;
+ return true;
+ } else if (macro == "num_services_in_downtime") {
+ *result = ss.services_in_downtime;
+ return true;
+ } else if (macro == "num_services_acknowledged") {
+ *result = ss.services_acknowledged;
+ return true;
+ } else if (macro == "num_services_handled") {
+ *result = ss.services_handled;
+ return true;
+ } else if (macro == "num_services_problem") {
+ *result = ss.services_problem;
+ return true;
+ }
+ }
+ else if (macro.Contains("num_hosts")) {
+ HostStatistics hs = CIB::CalculateHostStats();
+
+ if (macro == "num_hosts_up") {
+ *result = hs.hosts_up;
+ return true;
+ } else if (macro == "num_hosts_down") {
+ *result = hs.hosts_down;
+ return true;
+ } else if (macro == "num_hosts_pending") {
+ *result = hs.hosts_pending;
+ return true;
+ } else if (macro == "num_hosts_unreachable") {
+ *result = hs.hosts_unreachable;
+ return true;
+ } else if (macro == "num_hosts_flapping") {
+ *result = hs.hosts_flapping;
+ return true;
+ } else if (macro == "num_hosts_in_downtime") {
+ *result = hs.hosts_in_downtime;
+ return true;
+ } else if (macro == "num_hosts_acknowledged") {
+ *result = hs.hosts_acknowledged;
+ return true;
+ } else if (macro == "num_hosts_handled") {
+ *result = hs.hosts_handled;
+ return true;
+ } else if (macro == "num_hosts_problem") {
+ *result = hs.hosts_problem;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+String IcingaApplication::GetNodeName() const
+{
+ return ScriptGlobal::Get("NodeName");
+}
+
+/* Intentionally kept here, since an agent may not have the CheckerComponent loaded. */
+int IcingaApplication::GetMaxConcurrentChecks() const
+{
+ return ScriptGlobal::Get("MaxConcurrentChecks");
+}
+
+String IcingaApplication::GetEnvironment() const
+{
+ return Application::GetAppEnvironment();
+}
+
+void IcingaApplication::SetEnvironment(const String& value, bool suppress_events, const Value& cookie)
+{
+ Application::SetAppEnvironment(value);
+}
+
+void IcingaApplication::ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ MacroProcessor::ValidateCustomVars(this, lvalue());
+}
diff --git a/lib/icinga/icingaapplication.hpp b/lib/icinga/icingaapplication.hpp
new file mode 100644
index 0000000..7888fa6
--- /dev/null
+++ b/lib/icinga/icingaapplication.hpp
@@ -0,0 +1,52 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef ICINGAAPPLICATION_H
+#define ICINGAAPPLICATION_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/icingaapplication-ti.hpp"
+#include "icinga/macroresolver.hpp"
+
+namespace icinga
+{
+
+/**
+ * The Icinga application.
+ *
+ * @ingroup icinga
+ */
+class IcingaApplication final : public ObjectImpl<IcingaApplication>, public MacroResolver
+{
+public:
+ DECLARE_OBJECT(IcingaApplication);
+ DECLARE_OBJECTNAME(IcingaApplication);
+
+ static void StaticInitialize();
+
+ int Main() override;
+
+ static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata);
+
+ static IcingaApplication::Ptr GetInstance();
+
+ bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const override;
+
+ String GetNodeName() const;
+
+ int GetMaxConcurrentChecks() const;
+
+ String GetEnvironment() const override;
+ void SetEnvironment(const String& value, bool suppress_events = false, const Value& cookie = Empty) override;
+
+ void ValidateVars(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
+
+private:
+ void DumpProgramState();
+ void DumpModifiedAttributes();
+
+ void OnShutdown() override;
+};
+
+}
+
+#endif /* ICINGAAPPLICATION_H */
diff --git a/lib/icinga/icingaapplication.ti b/lib/icinga/icingaapplication.ti
new file mode 100644
index 0000000..1cdef74
--- /dev/null
+++ b/lib/icinga/icingaapplication.ti
@@ -0,0 +1,41 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "base/application.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class IcingaApplication : Application
+{
+ activation_priority -50;
+
+ [config, no_storage, virtual] String environment {
+ get;
+ set;
+ default {{{ return Application::GetAppEnvironment(); }}}
+ };
+
+ [config] bool enable_notifications {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_event_handlers {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_flapping {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_host_checks {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_service_checks {
+ default {{{ return true; }}}
+ };
+ [config] bool enable_perfdata {
+ default {{{ return true; }}}
+ };
+ [config] Dictionary::Ptr vars;
+};
+
+}
diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp
new file mode 100644
index 0000000..33e6665
--- /dev/null
+++ b/lib/icinga/legacytimeperiod.cpp
@@ -0,0 +1,644 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/legacytimeperiod.hpp"
+#include "base/function.hpp"
+#include "base/convert.hpp"
+#include "base/exception.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/debug.hpp"
+#include "base/utility.hpp"
+
+using namespace icinga;
+
+REGISTER_FUNCTION_NONCONST(Internal, LegacyTimePeriod, &LegacyTimePeriod::ScriptFunc, "tp:begin:end");
+
+/**
+ * Returns the same as mktime() but does not modify its argument and takes a const pointer.
+ *
+ * @param t struct tm to convert to time_t
+ * @return time_t representing the timestamp given by t
+ */
+static time_t mktime_const(const tm *t) {
+ tm copy = *t;
+ return mktime(&copy);
+}
+
+bool LegacyTimePeriod::IsInTimeRange(const tm *begin, const tm *end, int stride, const tm *reference)
+{
+ time_t tsbegin, tsend, tsref;
+ tsbegin = mktime_const(begin);
+ tsend = mktime_const(end);
+ tsref = mktime_const(reference);
+
+ if (tsref < tsbegin || tsref > tsend)
+ return false;
+
+ int daynumber = (tsref - tsbegin) / (24 * 60 * 60);
+
+ if (stride > 1 && daynumber % stride > 0)
+ return false;
+
+ return true;
+}
+
+/**
+ * Update all day-related fields of reference (tm_year, tm_mon, tm_mday, tm_wday, tm_yday) to reference the n-th
+ * occurrence of a weekday (given by wday) in the month represented by the original value of reference.
+ *
+ * If n is negative, counting is done from the end of the month, so for example with wday=1 and n=-1, the result will be
+ * the last Monday in the month given by reference.
+ *
+ * @param wday Weekday (0 = Sunday, 1 = Monday, ..., 6 = Saturday, like tm_wday)
+ * @param n Search the n-th weekday (given by wday) in the month given by reference
+ * @param reference Input for the current month and output for the given day of that moth
+ */
+void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference)
+{
+ // Work on a copy to only update specific fields of reference (as documented).
+ tm t = *reference;
+
+ int dir, seen = 0;
+
+ if (n > 0) {
+ dir = 1;
+ } else {
+ n *= -1;
+ dir = -1;
+
+ /* Negative days are relative to the next month. */
+ t.tm_mon++;
+ }
+
+ ASSERT(n > 0);
+
+ t.tm_mday = 1;
+
+ for (;;) {
+ // Always operate on 00:00:00 with automatic DST detection, otherwise days could
+ // be skipped or counted twice if +-24 hours is not on the next or previous day.
+ t.tm_hour = 0;
+ t.tm_min = 0;
+ t.tm_sec = 0;
+ t.tm_isdst = -1;
+
+ mktime(&t);
+
+ if (t.tm_wday == wday) {
+ seen++;
+
+ if (seen == n)
+ break;
+ }
+
+ t.tm_mday += dir;
+ }
+
+ reference->tm_year = t.tm_year;
+ reference->tm_mon = t.tm_mon;
+ reference->tm_mday = t.tm_mday;
+ reference->tm_wday = t.tm_wday;
+ reference->tm_yday = t.tm_yday;
+}
+
+int LegacyTimePeriod::WeekdayFromString(const String& daydef)
+{
+ if (daydef == "sunday")
+ return 0;
+ else if (daydef == "monday")
+ return 1;
+ else if (daydef == "tuesday")
+ return 2;
+ else if (daydef == "wednesday")
+ return 3;
+ else if (daydef == "thursday")
+ return 4;
+ else if (daydef == "friday")
+ return 5;
+ else if (daydef == "saturday")
+ return 6;
+ else
+ return -1;
+}
+
+int LegacyTimePeriod::MonthFromString(const String& monthdef)
+{
+ if (monthdef == "january")
+ return 0;
+ else if (monthdef == "february")
+ return 1;
+ else if (monthdef == "march")
+ return 2;
+ else if (monthdef == "april")
+ return 3;
+ else if (monthdef == "may")
+ return 4;
+ else if (monthdef == "june")
+ return 5;
+ else if (monthdef == "july")
+ return 6;
+ else if (monthdef == "august")
+ return 7;
+ else if (monthdef == "september")
+ return 8;
+ else if (monthdef == "october")
+ return 9;
+ else if (monthdef == "november")
+ return 10;
+ else if (monthdef == "december")
+ return 11;
+ else
+ return -1;
+}
+
+boost::gregorian::date LegacyTimePeriod::GetEndOfMonthDay(int year, int month)
+{
+ boost::gregorian::date d(boost::gregorian::greg_year(year), boost::gregorian::greg_month(month), 1);
+
+ return d.end_of_month();
+}
+
+/**
+ * Finds the first day on or after the day given by reference and writes the beginning and end time of that day to
+ * the output parameters begin and end.
+ *
+ * @param timespec Day to find, for example "2021-10-20", "sunday", ...
+ * @param begin if != nullptr, set to 00:00:00 on that day
+ * @param end if != nullptr, set to 24:00:00 on that day (i.e. 00:00:00 of the next day)
+ * @param reference Time to begin the search at
+ */
+void LegacyTimePeriod::ParseTimeSpec(const String& timespec, tm *begin, tm *end, const tm *reference)
+{
+ /* YYYY-MM-DD */
+ if (timespec.GetLength() == 10 && timespec[4] == '-' && timespec[7] == '-') {
+ int year = Convert::ToLong(timespec.SubStr(0, 4));
+ int month = Convert::ToLong(timespec.SubStr(5, 2));
+ int day = Convert::ToLong(timespec.SubStr(8, 2));
+
+ if (month < 1 || month > 12)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
+ if (day < 1 || day > 31)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid day in time specification: " + timespec));
+
+ if (begin) {
+ *begin = *reference;
+ begin->tm_year = year - 1900;
+ begin->tm_mon = month - 1;
+ begin->tm_mday = day;
+ begin->tm_hour = 0;
+ begin->tm_min = 0;
+ begin->tm_sec = 0;
+ begin->tm_isdst = -1;
+ }
+
+ if (end) {
+ *end = *reference;
+ end->tm_year = year - 1900;
+ end->tm_mon = month - 1;
+ end->tm_mday = day;
+ end->tm_hour = 24;
+ end->tm_min = 0;
+ end->tm_sec = 0;
+ end->tm_isdst = -1;
+ }
+
+ return;
+ }
+
+ std::vector<String> tokens = timespec.Split(" ");
+
+ int mon = -1;
+
+ if (tokens.size() > 1 && (tokens[0] == "day" || (mon = MonthFromString(tokens[0])) != -1)) {
+ if (mon == -1)
+ mon = reference->tm_mon;
+
+ int mday = Convert::ToLong(tokens[1]);
+
+ if (begin) {
+ *begin = *reference;
+ begin->tm_mon = mon;
+ begin->tm_mday = mday;
+ begin->tm_hour = 0;
+ begin->tm_min = 0;
+ begin->tm_sec = 0;
+ begin->tm_isdst = -1;
+
+ /* day -X: Negative days are relative to the next month. */
+ if (mday < 0) {
+ boost::gregorian::date d(GetEndOfMonthDay(reference->tm_year + 1900, mon + 1)); //TODO: Refactor this mess into full Boost.DateTime
+
+ //Depending on the number, we need to substract specific days (counting starts at 0).
+ d = d - boost::gregorian::days(mday * -1 - 1);
+
+ *begin = boost::gregorian::to_tm(d);
+ begin->tm_hour = 0;
+ begin->tm_min = 0;
+ begin->tm_sec = 0;
+ }
+ }
+
+ if (end) {
+ *end = *reference;
+ end->tm_mon = mon;
+ end->tm_mday = mday;
+ end->tm_hour = 24;
+ end->tm_min = 0;
+ end->tm_sec = 0;
+ end->tm_isdst = -1;
+
+ /* day -X: Negative days are relative to the next month. */
+ if (mday < 0) {
+ boost::gregorian::date d(GetEndOfMonthDay(reference->tm_year + 1900, mon + 1)); //TODO: Refactor this mess into full Boost.DateTime
+
+ //Depending on the number, we need to substract specific days (counting starts at 0).
+ d = d - boost::gregorian::days(mday * -1 - 1);
+
+ // End date is one day in the future, starting 00:00:00
+ d = d + boost::gregorian::days(1);
+
+ *end = boost::gregorian::to_tm(d);
+ end->tm_hour = 0;
+ end->tm_min = 0;
+ end->tm_sec = 0;
+ }
+ }
+
+ return;
+ }
+
+ int wday;
+
+ if (tokens.size() >= 1 && (wday = WeekdayFromString(tokens[0])) != -1) {
+ tm myref = *reference;
+ myref.tm_isdst = -1;
+
+ if (tokens.size() > 2) {
+ mon = MonthFromString(tokens[2]);
+
+ if (mon == -1)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
+
+ myref.tm_mon = mon;
+ }
+
+ int n = 0;
+
+ if (tokens.size() > 1)
+ n = Convert::ToLong(tokens[1]);
+
+ if (begin) {
+ *begin = myref;
+
+ if (tokens.size() > 1)
+ FindNthWeekday(wday, n, begin);
+ else
+ begin->tm_mday += (7 - begin->tm_wday + wday) % 7;
+
+ begin->tm_hour = 0;
+ begin->tm_min = 0;
+ begin->tm_sec = 0;
+ }
+
+ if (end) {
+ *end = myref;
+
+ if (tokens.size() > 1)
+ FindNthWeekday(wday, n, end);
+ else
+ end->tm_mday += (7 - end->tm_wday + wday) % 7;
+
+ end->tm_hour = 0;
+ end->tm_min = 0;
+ end->tm_sec = 0;
+ end->tm_mday++;
+ }
+
+ return;
+ }
+
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + timespec));
+}
+
+/**
+ * Parse a range of days.
+ *
+ * The input can have the following formats:
+ * begin
+ * begin - end
+ * begin / stride
+ * begin - end / stride
+ *
+ * @param timerange Text representation of a day range or a single day, for example "2021-10-20", "monday - friday", ...
+ * @param begin Output parameter set to 00:00:00 of the first day of the range
+ * @param end Output parameter set to 24:00:00 of the last day of the range (i.e. 00:00:00 of the day after)
+ * @param stride Output parameter for the stride (for every n-th day)
+ * @param reference Expand the range relative to this timestamp
+ */
+void LegacyTimePeriod::ParseTimeRange(const String& timerange, tm *begin, tm *end, int *stride, const tm *reference)
+{
+ String def = timerange;
+
+ /* Figure out the stride. */
+ size_t pos = def.FindFirstOf('/');
+
+ if (pos != String::NPos) {
+ String strStride = def.SubStr(pos + 1).Trim();
+ *stride = Convert::ToLong(strStride);
+
+ /* Remove the stride parameter from the definition. */
+ def = def.SubStr(0, pos);
+ } else {
+ *stride = 1; /* User didn't specify anything, assume default. */
+ }
+
+ /* Figure out whether the user has specified two dates. */
+ pos = def.Find("- ");
+
+ if (pos != String::NPos) {
+ String first = def.SubStr(0, pos).Trim();
+
+ String second = def.SubStr(pos + 1).Trim();
+
+ ParseTimeSpec(first, begin, nullptr, reference);
+
+ /* If the second definition starts with a number we need
+ * to add the first word from the first definition, e.g.:
+ * day 1 - 15 --> "day 15" */
+ bool is_number = true;
+ size_t xpos = second.FindFirstOf(' ');
+ String fword = second.SubStr(0, xpos);
+
+ try {
+ Convert::ToLong(fword);
+ } catch (...) {
+ is_number = false;
+ }
+
+ if (is_number) {
+ xpos = first.FindFirstOf(' ');
+ ASSERT(xpos != String::NPos);
+ second = first.SubStr(0, xpos + 1) + second;
+ }
+
+ ParseTimeSpec(second, nullptr, end, reference);
+ } else {
+ ParseTimeSpec(def, begin, end, reference);
+ }
+}
+
+bool LegacyTimePeriod::IsInDayDefinition(const String& daydef, const tm *reference)
+{
+ tm begin, end;
+ int stride;
+
+ ParseTimeRange(daydef, &begin, &end, &stride, reference);
+
+ Log(LogDebug, "LegacyTimePeriod")
+ << "ParseTimeRange: '" << daydef << "' => " << mktime(&begin)
+ << " -> " << mktime(&end) << ", stride: " << stride;
+
+ return IsInTimeRange(&begin, &end, stride, reference);
+}
+
+static inline
+void ProcessTimeRaw(const String& in, const tm *reference, tm *out)
+{
+ *out = *reference;
+
+ auto hd (in.Split(":"));
+
+ switch (hd.size()) {
+ case 2:
+ out->tm_sec = 0;
+ break;
+ case 3:
+ out->tm_sec = Convert::ToLong(hd[2]);
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + in));
+ }
+
+ out->tm_hour = Convert::ToLong(hd[0]);
+ out->tm_min = Convert::ToLong(hd[1]);
+}
+
+void LegacyTimePeriod::ProcessTimeRangeRaw(const String& timerange, const tm *reference, tm *begin, tm *end)
+{
+ std::vector<String> times = timerange.Split("-");
+
+ if (times.size() != 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid timerange: " + timerange));
+
+ ProcessTimeRaw(times[0], reference, begin);
+ ProcessTimeRaw(times[1], reference, end);
+
+ if (begin->tm_hour * 3600 + begin->tm_min * 60 + begin->tm_sec >=
+ end->tm_hour * 3600 + end->tm_min * 60 + end->tm_sec)
+ end->tm_hour += 24;
+}
+
+Dictionary::Ptr LegacyTimePeriod::ProcessTimeRange(const String& timestamp, const tm *reference)
+{
+ tm begin, end;
+
+ ProcessTimeRangeRaw(timestamp, reference, &begin, &end);
+
+ return new Dictionary({
+ { "begin", (long)mktime(&begin) },
+ { "end", (long)mktime(&end) }
+ });
+}
+
+/**
+ * Takes a list of timeranges end expands them to concrete timestamp based on a reference time.
+ *
+ * @param timeranges String of comma separated time ranges, for example "10:00-12:00", "12:15:30-12:23:43,16:00-18:00"
+ * @param reference Starting point for searching the segments
+ * @param result For each range, a dict with keys "begin" and "end" is added
+ */
+void LegacyTimePeriod::ProcessTimeRanges(const String& timeranges, const tm *reference, const Array::Ptr& result)
+{
+ std::vector<String> ranges = timeranges.Split(",");
+
+ for (const String& range : ranges) {
+ Dictionary::Ptr segment = ProcessTimeRange(range, reference);
+
+ if (segment->Get("begin") >= segment->Get("end"))
+ continue;
+
+ result->Add(segment);
+ }
+}
+
+Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const String& timeranges, const tm *reference)
+{
+ tm begin, end, iter;
+ time_t tsend, tsiter, tsref;
+ int stride;
+
+ tsref = mktime_const(reference);
+
+ ParseTimeRange(daydef, &begin, &end, &stride, reference);
+
+ iter = begin;
+
+ tsend = mktime(&end);
+
+ do {
+ if (IsInTimeRange(&begin, &end, stride, &iter)) {
+ Array::Ptr segments = new Array();
+ ProcessTimeRanges(timeranges, &iter, segments);
+
+ Dictionary::Ptr bestSegment;
+ double bestEnd = 0.0;
+
+ ObjectLock olock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ double begin = segment->Get("begin");
+ double end = segment->Get("end");
+
+ if (begin >= tsref || end < tsref)
+ continue;
+
+ if (!bestSegment || end > bestEnd) {
+ bestSegment = segment;
+ bestEnd = end;
+ }
+ }
+
+ if (bestSegment)
+ return bestSegment;
+ }
+
+ iter.tm_mday++;
+ iter.tm_hour = 0;
+ iter.tm_min = 0;
+ iter.tm_sec = 0;
+ tsiter = mktime(&iter);
+ } while (tsiter < tsend);
+
+ return nullptr;
+}
+
+Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, const tm *reference)
+{
+ tm begin, end, iter, ref;
+ time_t tsend, tsiter, tsref;
+ int stride;
+
+ for (int pass = 1; pass <= 2; pass++) {
+ if (pass == 1) {
+ ref = *reference;
+ } else {
+ ref = end;
+ ref.tm_mday++;
+ }
+
+ tsref = mktime(&ref);
+
+ ParseTimeRange(daydef, &begin, &end, &stride, &ref);
+
+ iter = begin;
+
+ tsend = mktime(&end);
+
+ do {
+ if (IsInTimeRange(&begin, &end, stride, &iter)) {
+ Array::Ptr segments = new Array();
+ ProcessTimeRanges(timeranges, &iter, segments);
+
+ Dictionary::Ptr bestSegment;
+ double bestBegin;
+
+ ObjectLock olock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ double begin = segment->Get("begin");
+
+ if (begin < tsref)
+ continue;
+
+ if (!bestSegment || begin < bestBegin) {
+ bestSegment = segment;
+ bestBegin = begin;
+ }
+ }
+
+ if (bestSegment)
+ return bestSegment;
+ }
+
+ iter.tm_mday++;
+ iter.tm_hour = 0;
+ iter.tm_min = 0;
+ iter.tm_sec = 0;
+ tsiter = mktime(&iter);
+ } while (tsiter < tsend);
+ }
+
+ return nullptr;
+}
+
+Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, double end)
+{
+ Array::Ptr segments = new Array();
+
+ Dictionary::Ptr ranges = tp->GetRanges();
+
+ if (ranges) {
+ tm tm_begin = Utility::LocalTime(begin);
+
+ // Always evaluate time periods for full days as their ranges are given per day.
+ tm_begin.tm_hour = 0;
+ tm_begin.tm_min = 0;
+ tm_begin.tm_sec = 0;
+ tm_begin.tm_isdst = -1;
+
+ // Helper to move a struct tm to midnight of the next day for the loop below.
+ // Due to DST changes, this may move the time by something else than 24 hours.
+ auto advance_to_next_day = [](tm *t) {
+ t->tm_mday++;
+ t->tm_hour = 0;
+ t->tm_min = 0;
+ t->tm_sec = 0;
+ t->tm_isdst = -1;
+
+ // Normalize fields using mktime.
+ mktime(t);
+
+ // Reset tm_isdst so that future calls figure out the correct time zone after setting tm_hour/tm_min/tm_sec.
+ t->tm_isdst = -1;
+ };
+
+ for (tm reference = tm_begin; mktime_const(&reference) <= end; advance_to_next_day(&reference)) {
+
+#ifdef I2_DEBUG
+ Log(LogDebug, "LegacyTimePeriod")
+ << "Checking reference time " << mktime_const(&reference);
+#endif /* I2_DEBUG */
+
+ ObjectLock olock(ranges);
+ for (const Dictionary::Pair& kv : ranges) {
+ if (!IsInDayDefinition(kv.first, &reference)) {
+#ifdef I2_DEBUG
+ Log(LogDebug, "LegacyTimePeriod")
+ << "Not in day definition '" << kv.first << "'.";
+#endif /* I2_DEBUG */
+ continue;
+ }
+
+#ifdef I2_DEBUG
+ Log(LogDebug, "LegacyTimePeriod")
+ << "In day definition '" << kv.first << "'.";
+#endif /* I2_DEBUG */
+
+ ProcessTimeRanges(kv.second, &reference, segments);
+ }
+ }
+ }
+
+ Log(LogDebug, "LegacyTimePeriod")
+ << "Legacy timeperiod update returned " << segments->GetLength() << " segments.";
+
+ return segments;
+}
diff --git a/lib/icinga/legacytimeperiod.hpp b/lib/icinga/legacytimeperiod.hpp
new file mode 100644
index 0000000..001eb5c
--- /dev/null
+++ b/lib/icinga/legacytimeperiod.hpp
@@ -0,0 +1,45 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef LEGACYTIMEPERIOD_H
+#define LEGACYTIMEPERIOD_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/timeperiod.hpp"
+#include "base/dictionary.hpp"
+#include <boost/date_time/gregorian/gregorian.hpp>
+
+namespace icinga
+{
+
+/**
+ * Implements Icinga 1.x time periods.
+ *
+ * @ingroup icinga
+ */
+class LegacyTimePeriod
+{
+public:
+ static Array::Ptr ScriptFunc(const TimePeriod::Ptr& tp, double start, double end);
+
+ static bool IsInTimeRange(const tm *begin, const tm *end, int stride, const tm *reference);
+ static void FindNthWeekday(int wday, int n, tm *reference);
+ static int WeekdayFromString(const String& daydef);
+ static int MonthFromString(const String& monthdef);
+ static void ParseTimeSpec(const String& timespec, tm *begin, tm *end, const tm *reference);
+ static void ParseTimeRange(const String& timerange, tm *begin, tm *end, int *stride, const tm *reference);
+ static bool IsInDayDefinition(const String& daydef, const tm *reference);
+ static void ProcessTimeRangeRaw(const String& timerange, const tm *reference, tm *begin, tm *end);
+ static Dictionary::Ptr ProcessTimeRange(const String& timerange, const tm *reference);
+ static void ProcessTimeRanges(const String& timeranges, const tm *reference, const Array::Ptr& result);
+ static Dictionary::Ptr FindNextSegment(const String& daydef, const String& timeranges, const tm *reference);
+ static Dictionary::Ptr FindRunningSegment(const String& daydef, const String& timeranges, const tm *reference);
+
+private:
+ LegacyTimePeriod();
+
+ static boost::gregorian::date GetEndOfMonthDay(int year, int month);
+};
+
+}
+
+#endif /* LEGACYTIMEPERIOD_H */
diff --git a/lib/icinga/macroprocessor.cpp b/lib/icinga/macroprocessor.cpp
new file mode 100644
index 0000000..724a4f9
--- /dev/null
+++ b/lib/icinga/macroprocessor.cpp
@@ -0,0 +1,585 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/macroprocessor.hpp"
+#include "icinga/macroresolver.hpp"
+#include "icinga/customvarobject.hpp"
+#include "icinga/envresolver.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "base/array.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/configobject.hpp"
+#include "base/scriptframe.hpp"
+#include "base/convert.hpp"
+#include "base/exception.hpp"
+#include <boost/algorithm/string/join.hpp>
+
+using namespace icinga;
+
+thread_local Dictionary::Ptr MacroResolver::OverrideMacros;
+
+Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr, String *missingMacro,
+ const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
+ bool useResolvedMacros, int recursionLevel)
+{
+ if (useResolvedMacros)
+ REQUIRE_NOT_NULL(resolvedMacros);
+
+ Value result;
+
+ if (str.IsEmpty())
+ return Empty;
+
+ if (str.IsScalar()) {
+ result = InternalResolveMacros(str, resolvers, cr, missingMacro, escapeFn,
+ resolvedMacros, useResolvedMacros, recursionLevel + 1);
+ } else if (str.IsObjectType<Array>()) {
+ ArrayData resultArr;
+ Array::Ptr arr = str;
+
+ ObjectLock olock(arr);
+
+ for (const Value& arg : arr) {
+ /* Note: don't escape macros here. */
+ Value value = InternalResolveMacros(arg, resolvers, cr, missingMacro,
+ EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1);
+
+ if (value.IsObjectType<Array>())
+ resultArr.push_back(Utility::Join(value, ';'));
+ else
+ resultArr.push_back(value);
+ }
+
+ result = new Array(std::move(resultArr));
+ } else if (str.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr resultDict = new Dictionary();
+ Dictionary::Ptr dict = str;
+
+ ObjectLock olock(dict);
+
+ for (const Dictionary::Pair& kv : dict) {
+ /* Note: don't escape macros here. */
+ resultDict->Set(kv.first, InternalResolveMacros(kv.second, resolvers, cr, missingMacro,
+ EscapeCallback(), resolvedMacros, useResolvedMacros, recursionLevel + 1));
+ }
+
+ result = resultDict;
+ } else if (str.IsObjectType<Function>()) {
+ result = EvaluateFunction(str, resolvers, cr, escapeFn, resolvedMacros, useResolvedMacros, 0);
+ } else {
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Macro is not a string or array."));
+ }
+
+ return result;
+}
+
+static const EnvResolver::Ptr l_EnvResolver = new EnvResolver();
+
+static MacroProcessor::ResolverList GetDefaultResolvers()
+{
+ return {
+ { "icinga", IcingaApplication::GetInstance() },
+ { "env", l_EnvResolver, false }
+ };
+}
+
+bool MacroProcessor::ResolveMacro(const String& macro, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr, Value *result, bool *recursive_macro)
+{
+ CONTEXT("Resolving macro '" << macro << "'");
+
+ *recursive_macro = false;
+
+ std::vector<String> tokens = macro.Split(".");
+
+ String objName;
+ if (tokens.size() > 1) {
+ objName = tokens[0];
+ tokens.erase(tokens.begin());
+ }
+
+ const auto defaultResolvers (GetDefaultResolvers());
+
+ for (auto resolverList : {&resolvers, &defaultResolvers}) {
+ for (auto& resolver : *resolverList) {
+ if (!objName.IsEmpty() && objName != resolver.Name)
+ continue;
+
+ if (objName.IsEmpty()) {
+ if (!resolver.ResolveShortMacros)
+ continue;
+
+ Dictionary::Ptr vars;
+ CustomVarObject::Ptr dobj = dynamic_pointer_cast<CustomVarObject>(resolver.Obj);
+
+ if (dobj) {
+ vars = dobj->GetVars();
+ } else {
+ auto app (dynamic_pointer_cast<IcingaApplication>(resolver.Obj));
+
+ if (app) {
+ vars = app->GetVars();
+ }
+ }
+
+ if (vars && vars->Contains(macro)) {
+ *result = vars->Get(macro);
+ *recursive_macro = true;
+ return true;
+ }
+ }
+
+ auto *mresolver = dynamic_cast<MacroResolver *>(resolver.Obj.get());
+
+ if (mresolver && mresolver->ResolveMacro(boost::algorithm::join(tokens, "."), cr, result))
+ return true;
+
+ Value ref = resolver.Obj;
+ bool valid = true;
+
+ for (const String& token : tokens) {
+ if (ref.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr dict = ref;
+ if (dict->Contains(token)) {
+ ref = dict->Get(token);
+ continue;
+ } else {
+ valid = false;
+ break;
+ }
+ } else if (ref.IsObject()) {
+ Object::Ptr object = ref;
+
+ Type::Ptr type = object->GetReflectionType();
+
+ if (!type) {
+ valid = false;
+ break;
+ }
+
+ int field = type->GetFieldId(token);
+
+ if (field == -1) {
+ valid = false;
+ break;
+ }
+
+ ref = object->GetField(field);
+
+ Field fieldInfo = type->GetFieldInfo(field);
+
+ if (strcmp(fieldInfo.TypeName, "Timestamp") == 0)
+ ref = static_cast<long>(ref);
+ }
+ }
+
+ if (valid) {
+ if (tokens[0] == "vars" ||
+ tokens[0] == "action_url" ||
+ tokens[0] == "notes_url" ||
+ tokens[0] == "notes")
+ *recursive_macro = true;
+
+ *result = ref;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+Value MacroProcessor::EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
+{
+ Dictionary::Ptr resolvers_this = new Dictionary();
+ const auto defaultResolvers (GetDefaultResolvers());
+
+ for (auto resolverList : {&resolvers, &defaultResolvers}) {
+ for (auto& resolver: *resolverList) {
+ resolvers_this->Set(resolver.Name, resolver.Obj);
+ }
+ }
+
+ auto internalResolveMacrosShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
+ if (args.size() < 1)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
+
+ String missingMacro;
+
+ return MacroProcessor::InternalResolveMacros(args[0], resolvers, cr, &missingMacro, MacroProcessor::EscapeCallback(),
+ resolvedMacros, useResolvedMacros, recursionLevel);
+ };
+
+ resolvers_this->Set("macro", new Function("macro (temporary)", internalResolveMacrosShim, { "str" }));
+
+ auto internalResolveArgumentsShim = [resolvers, cr, resolvedMacros, useResolvedMacros, recursionLevel](const std::vector<Value>& args) {
+ if (args.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function"));
+
+ return MacroProcessor::ResolveArguments(args[0], args[1], resolvers, cr,
+ resolvedMacros, useResolvedMacros, recursionLevel + 1);
+ };
+
+ resolvers_this->Set("resolve_arguments", new Function("resolve_arguments (temporary)", internalResolveArgumentsShim, { "command", "args" }));
+
+ return func->InvokeThis(resolvers_this);
+}
+
+Value MacroProcessor::InternalResolveMacros(const String& str, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr, String *missingMacro,
+ const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
+ bool useResolvedMacros, int recursionLevel)
+{
+ CONTEXT("Resolving macros for string '" << str << "'");
+
+ if (recursionLevel > 15)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Infinite recursion detected while resolving macros"));
+
+ size_t offset, pos_first, pos_second;
+ offset = 0;
+
+ Dictionary::Ptr resolvers_this;
+
+ String result = str;
+ while ((pos_first = result.FindFirstOf("$", offset)) != String::NPos) {
+ pos_second = result.FindFirstOf("$", pos_first + 1);
+
+ if (pos_second == String::NPos)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Closing $ not found in macro format string."));
+
+ String name = result.SubStr(pos_first + 1, pos_second - pos_first - 1);
+
+ Value resolved_macro;
+ bool recursive_macro;
+ bool found;
+
+ if (useResolvedMacros) {
+ recursive_macro = false;
+ found = resolvedMacros->Contains(name);
+
+ if (found)
+ resolved_macro = resolvedMacros->Get(name);
+ } else
+ found = ResolveMacro(name, resolvers, cr, &resolved_macro, &recursive_macro);
+
+ /* $$ is an escape sequence for $. */
+ if (name.IsEmpty()) {
+ resolved_macro = "$";
+ found = true;
+ }
+
+ if (resolved_macro.IsObjectType<Function>()) {
+ resolved_macro = EvaluateFunction(resolved_macro, resolvers, cr, escapeFn,
+ resolvedMacros, useResolvedMacros, recursionLevel + 1);
+ }
+
+ if (!found) {
+ if (!missingMacro)
+ Log(LogWarning, "MacroProcessor")
+ << "Macro '" << name << "' is not defined.";
+ else
+ *missingMacro = name;
+ }
+
+ /* recursively resolve macros in the macro if it was a user macro */
+ if (recursive_macro) {
+ if (resolved_macro.IsObjectType<Array>()) {
+ Array::Ptr arr = resolved_macro;
+ ArrayData resolved_arr;
+
+ ObjectLock olock(arr);
+ for (const Value& value : arr) {
+ if (value.IsScalar()) {
+ resolved_arr.push_back(InternalResolveMacros(value,
+ resolvers, cr, missingMacro, EscapeCallback(), nullptr,
+ false, recursionLevel + 1));
+ } else
+ resolved_arr.push_back(value);
+ }
+
+ resolved_macro = new Array(std::move(resolved_arr));
+ } else if (resolved_macro.IsString()) {
+ resolved_macro = InternalResolveMacros(resolved_macro,
+ resolvers, cr, missingMacro, EscapeCallback(), nullptr,
+ false, recursionLevel + 1);
+ }
+ }
+
+ if (!useResolvedMacros && found && resolvedMacros)
+ resolvedMacros->Set(name, resolved_macro);
+
+ if (escapeFn)
+ resolved_macro = escapeFn(resolved_macro);
+
+ /* we're done if this is the only macro and there are no other non-macro parts in the string */
+ if (pos_first == 0 && pos_second == str.GetLength() - 1)
+ return resolved_macro;
+ else if (resolved_macro.IsObjectType<Array>())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
+
+ if (resolved_macro.IsObjectType<Array>()) {
+ /* don't allow mixing strings and arrays in macro strings */
+ if (pos_first != 0 || pos_second != str.GetLength() - 1)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Mixing both strings and non-strings in macros is not allowed."));
+
+ return resolved_macro;
+ }
+
+ String resolved_macro_str = resolved_macro;
+
+ result.Replace(pos_first, pos_second - pos_first + 1, resolved_macro_str);
+ offset = pos_first + resolved_macro_str.GetLength();
+ }
+
+ return result;
+}
+
+
+bool MacroProcessor::ValidateMacroString(const String& macro)
+{
+ if (macro.IsEmpty())
+ return true;
+
+ size_t pos_first, pos_second, offset;
+ offset = 0;
+
+ while ((pos_first = macro.FindFirstOf("$", offset)) != String::NPos) {
+ pos_second = macro.FindFirstOf("$", pos_first + 1);
+
+ if (pos_second == String::NPos)
+ return false;
+
+ offset = pos_second + 1;
+ }
+
+ return true;
+}
+
+void MacroProcessor::ValidateCustomVars(const ConfigObject::Ptr& object, const Dictionary::Ptr& value)
+{
+ if (!value)
+ return;
+
+ /* string, array, dictionary */
+ ObjectLock olock(value);
+ for (const Dictionary::Pair& kv : value) {
+ const Value& varval = kv.second;
+
+ if (varval.IsObjectType<Dictionary>()) {
+ /* only one dictonary level */
+ Dictionary::Ptr varval_dict = varval;
+
+ ObjectLock xlock(varval_dict);
+ for (const Dictionary::Pair& kv_var : varval_dict) {
+ if (!kv_var.second.IsString())
+ continue;
+
+ if (!ValidateMacroString(kv_var.second))
+ BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first, kv_var.first }, "Closing $ not found in macro format string '" + kv_var.second + "'."));
+ }
+ } else if (varval.IsObjectType<Array>()) {
+ /* check all array entries */
+ Array::Ptr varval_arr = varval;
+
+ ObjectLock ylock (varval_arr);
+ for (const Value& arrval : varval_arr) {
+ if (!arrval.IsString())
+ continue;
+
+ if (!ValidateMacroString(arrval)) {
+ BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + arrval + "'."));
+ }
+ }
+ } else {
+ if (!varval.IsString())
+ continue;
+
+ if (!ValidateMacroString(varval))
+ BOOST_THROW_EXCEPTION(ValidationError(object.get(), { "vars", kv.first }, "Closing $ not found in macro format string '" + varval + "'."));
+ }
+ }
+}
+
+void MacroProcessor::AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
+ bool add_key, bool add_value, const Value& separator)
+{
+ if (add_key && separator.GetType() != ValueEmpty && add_value) {
+ args->Add(key + separator + value);
+ } else {
+ if (add_key)
+ args->Add(key);
+
+ if (add_value)
+ args->Add(value);
+ }
+}
+
+Value MacroProcessor::EscapeMacroShellArg(const Value& value)
+{
+ String result;
+
+ if (value.IsObjectType<Array>()) {
+ Array::Ptr arr = value;
+
+ ObjectLock olock(arr);
+ for (const Value& arg : arr) {
+ if (result.GetLength() > 0)
+ result += " ";
+
+ result += Utility::EscapeShellArg(arg);
+ }
+ } else
+ result = Utility::EscapeShellArg(value);
+
+ return result;
+}
+
+struct CommandArgument
+{
+ int Order{0};
+ bool SkipKey{false};
+ bool RepeatKey{true};
+ bool SkipValue{false};
+ String Key;
+ Value Separator;
+ Value AValue;
+
+ bool operator<(const CommandArgument& rhs) const
+ {
+ return Order < rhs.Order;
+ }
+};
+
+Value MacroProcessor::ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
+ const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel)
+{
+ if (useResolvedMacros)
+ REQUIRE_NOT_NULL(resolvedMacros);
+
+ Value resolvedCommand;
+ if (!arguments || command.IsObjectType<Array>() || command.IsObjectType<Function>())
+ resolvedCommand = MacroProcessor::ResolveMacros(command, resolvers, cr, nullptr,
+ EscapeMacroShellArg, resolvedMacros, useResolvedMacros, recursionLevel + 1);
+ else {
+ resolvedCommand = new Array({ command });
+ }
+
+ if (arguments) {
+ std::vector<CommandArgument> args;
+
+ ObjectLock olock(arguments);
+ for (const Dictionary::Pair& kv : arguments) {
+ const Value& arginfo = kv.second;
+
+ CommandArgument arg;
+ arg.Key = kv.first;
+
+ bool required = false;
+ Value argval;
+
+ if (arginfo.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr argdict = arginfo;
+ if (argdict->Contains("key"))
+ arg.Key = argdict->Get("key");
+ argval = argdict->Get("value");
+ if (argdict->Contains("required"))
+ required = argdict->Get("required");
+ arg.SkipKey = argdict->Get("skip_key");
+ if (argdict->Contains("repeat_key"))
+ arg.RepeatKey = argdict->Get("repeat_key");
+ arg.Order = argdict->Get("order");
+ arg.Separator = argdict->Get("separator");
+
+ Value set_if = argdict->Get("set_if");
+
+ if (!set_if.IsEmpty()) {
+ String missingMacro;
+ Value set_if_resolved = MacroProcessor::ResolveMacros(set_if, resolvers,
+ cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
+ useResolvedMacros, recursionLevel + 1);
+
+ if (!missingMacro.IsEmpty())
+ continue;
+
+ int value;
+
+ if (set_if_resolved == "true")
+ value = 1;
+ else if (set_if_resolved == "false")
+ value = 0;
+ else {
+ try {
+ value = Convert::ToLong(set_if_resolved);
+ } catch (const std::exception& ex) {
+ /* tried to convert a string */
+ Log(LogWarning, "PluginUtility")
+ << "Error evaluating set_if value '" << set_if_resolved
+ << "' used in argument '" << arg.Key << "': " << ex.what();
+ continue;
+ }
+ }
+
+ if (!value)
+ continue;
+ }
+ }
+ else
+ argval = arginfo;
+
+ if (argval.IsEmpty())
+ arg.SkipValue = true;
+
+ String missingMacro;
+ arg.AValue = MacroProcessor::ResolveMacros(argval, resolvers,
+ cr, &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
+ useResolvedMacros, recursionLevel + 1);
+
+ if (!missingMacro.IsEmpty()) {
+ if (required) {
+ BOOST_THROW_EXCEPTION(ScriptError("Non-optional macro '" + missingMacro + "' used in argument '" +
+ arg.Key + "' is missing."));
+ }
+
+ continue;
+ }
+
+ args.emplace_back(std::move(arg));
+ }
+
+ std::sort(args.begin(), args.end());
+
+ Array::Ptr command_arr = resolvedCommand;
+ for (const CommandArgument& arg : args) {
+
+ if (arg.AValue.IsObjectType<Dictionary>()) {
+ Log(LogWarning, "PluginUtility")
+ << "Tried to use dictionary in argument '" << arg.Key << "'.";
+ continue;
+ } else if (arg.AValue.IsObjectType<Array>()) {
+ bool first = true;
+ Array::Ptr arr = static_cast<Array::Ptr>(arg.AValue);
+
+ ObjectLock olock(arr);
+ for (const Value& value : arr) {
+ bool add_key;
+
+ if (first) {
+ first = false;
+ add_key = !arg.SkipKey;
+ } else
+ add_key = !arg.SkipKey && arg.RepeatKey;
+
+ AddArgumentHelper(command_arr, arg.Key, value, add_key, !arg.SkipValue, arg.Separator);
+ }
+ } else
+ AddArgumentHelper(command_arr, arg.Key, arg.AValue, !arg.SkipKey, !arg.SkipValue, arg.Separator);
+ }
+ }
+
+ return resolvedCommand;
+}
diff --git a/lib/icinga/macroprocessor.hpp b/lib/icinga/macroprocessor.hpp
new file mode 100644
index 0000000..7e74821
--- /dev/null
+++ b/lib/icinga/macroprocessor.hpp
@@ -0,0 +1,75 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef MACROPROCESSOR_H
+#define MACROPROCESSOR_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/checkable.hpp"
+#include "base/value.hpp"
+#include <vector>
+#include <utility>
+
+namespace icinga
+{
+
+/**
+ * Resolves macros.
+ *
+ * @ingroup icinga
+ */
+class MacroProcessor
+{
+public:
+ struct ResolverSpec
+ {
+ String Name;
+ Object::Ptr Obj;
+
+ // Whether to resolve not only e.g. $host.address$, but also just $address$
+ bool ResolveShortMacros;
+
+ inline ResolverSpec(String name, Object::Ptr obj, bool resolveShortMacros = true)
+ : Name(std::move(name)), Obj(std::move(obj)), ResolveShortMacros(resolveShortMacros)
+ {
+ }
+ };
+
+ typedef std::function<Value (const Value&)> EscapeCallback;
+ typedef std::vector<ResolverSpec> ResolverList;
+
+ static Value ResolveMacros(const Value& str, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr = nullptr, String *missingMacro = nullptr,
+ const EscapeCallback& escapeFn = EscapeCallback(),
+ const Dictionary::Ptr& resolvedMacros = nullptr,
+ bool useResolvedMacros = false, int recursionLevel = 0);
+
+ static Value ResolveArguments(const Value& command, const Dictionary::Ptr& arguments,
+ const MacroProcessor::ResolverList& resolvers, const CheckResult::Ptr& cr,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel = 0);
+
+ static bool ValidateMacroString(const String& macro);
+ static void ValidateCustomVars(const ConfigObject::Ptr& object, const Dictionary::Ptr& value);
+
+private:
+ MacroProcessor();
+
+ static bool ResolveMacro(const String& macro, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr, Value *result, bool *recursive_macro);
+ static Value InternalResolveMacros(const String& str,
+ const ResolverList& resolvers, const CheckResult::Ptr& cr,
+ String *missingMacro, const EscapeCallback& escapeFn,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros,
+ int recursionLevel = 0);
+ static Value EvaluateFunction(const Function::Ptr& func, const ResolverList& resolvers,
+ const CheckResult::Ptr& cr, const MacroProcessor::EscapeCallback& escapeFn,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int recursionLevel);
+
+ static void AddArgumentHelper(const Array::Ptr& args, const String& key, const String& value,
+ bool add_key, bool add_value, const Value& separator);
+ static Value EscapeMacroShellArg(const Value& value);
+
+};
+
+}
+
+#endif /* MACROPROCESSOR_H */
diff --git a/lib/icinga/macroresolver.hpp b/lib/icinga/macroresolver.hpp
new file mode 100644
index 0000000..62cd41d
--- /dev/null
+++ b/lib/icinga/macroresolver.hpp
@@ -0,0 +1,31 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef MACRORESOLVER_H
+#define MACRORESOLVER_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/checkresult.hpp"
+#include "base/dictionary.hpp"
+#include "base/string.hpp"
+
+namespace icinga
+{
+
+/**
+ * Resolves macros.
+ *
+ * @ingroup icinga
+ */
+class MacroResolver
+{
+public:
+ DECLARE_PTR_TYPEDEFS(MacroResolver);
+
+ static thread_local Dictionary::Ptr OverrideMacros;
+
+ virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const = 0;
+};
+
+}
+
+#endif /* MACRORESOLVER_H */
diff --git a/lib/icinga/notification-apply.cpp b/lib/icinga/notification-apply.cpp
new file mode 100644
index 0000000..f5b3764
--- /dev/null
+++ b/lib/icinga/notification-apply.cpp
@@ -0,0 +1,161 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/notification.hpp"
+#include "icinga/service.hpp"
+#include "config/configitembuilder.hpp"
+#include "config/applyrule.hpp"
+#include "base/initialize.hpp"
+#include "base/configtype.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/workqueue.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+INITIALIZE_ONCE([]() {
+ ApplyRule::RegisterType("Notification", { "Host", "Service" });
+});
+
+bool Notification::EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter)
+{
+ if (!skipFilter && !rule.EvaluateFilter(frame))
+ return false;
+
+ auto& di (rule.GetDebugInfo());
+
+#ifdef _DEBUG
+ Log(LogDebug, "Notification")
+ << "Applying notification '" << name << "' to object '" << checkable->GetName() << "' for rule " << di;
+#endif /* _DEBUG */
+
+ ConfigItemBuilder builder{di};
+ builder.SetType(Notification::TypeInstance);
+ builder.SetName(name);
+ builder.SetScope(frame.Locals->ShallowClone());
+ builder.SetIgnoreOnError(rule.GetIgnoreOnError());
+
+ builder.AddExpression(new ImportDefaultTemplatesExpression());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "host_name"), OpSetLiteral, MakeLiteral(host->GetName()), di));
+
+ if (service)
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "service_name"), OpSetLiteral, MakeLiteral(service->GetShortName()), di));
+
+ String zone = checkable->GetZoneName();
+
+ if (!zone.IsEmpty())
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "zone"), OpSetLiteral, MakeLiteral(zone), di));
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "package"), OpSetLiteral, MakeLiteral(rule.GetPackage()), di));
+
+ builder.AddExpression(new OwnedExpression(rule.GetExpression()));
+
+ ConfigItem::Ptr notificationItem = builder.Compile();
+ notificationItem->Register();
+
+ return true;
+}
+
+bool Notification::EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter)
+{
+ auto& di (rule.GetDebugInfo());
+
+ CONTEXT("Evaluating 'apply' rule (" << di << ")");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ ScriptFrame frame(true);
+ if (rule.GetScope())
+ rule.GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("host", host);
+ if (service)
+ frame.Locals->Set("service", service);
+
+ Value vinstances;
+
+ if (rule.GetFTerm()) {
+ try {
+ vinstances = rule.GetFTerm()->Evaluate(frame);
+ } catch (const std::exception&) {
+ /* Silently ignore errors here and assume there are no instances. */
+ return false;
+ }
+ } else {
+ vinstances = new Array({ "" });
+ }
+
+ bool match = false;
+
+ if (vinstances.IsObjectType<Array>()) {
+ if (!rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di));
+
+ Array::Ptr arr = vinstances;
+
+ ObjectLock olock(arr);
+ for (const Value& instance : arr) {
+ String name = rule.GetName();
+
+ if (!rule.GetFKVar().IsEmpty()) {
+ frame.Locals->Set(rule.GetFKVar(), instance);
+ name += instance;
+ }
+
+ if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter))
+ match = true;
+ }
+ } else if (vinstances.IsObjectType<Dictionary>()) {
+ if (rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di));
+
+ Dictionary::Ptr dict = vinstances;
+
+ for (const String& key : dict->GetKeys()) {
+ frame.Locals->Set(rule.GetFKVar(), key);
+ frame.Locals->Set(rule.GetFVVar(), dict->Get(key));
+
+ if (EvaluateApplyRuleInstance(checkable, rule.GetName() + key, frame, rule, skipFilter))
+ match = true;
+ }
+ }
+
+ return match;
+}
+
+void Notification::EvaluateApplyRules(const Host::Ptr& host)
+{
+ CONTEXT("Evaluating 'apply' rules for host '" << host->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(Notification::TypeInstance, Host::TypeInstance))
+ {
+ if (EvaluateApplyRule(host, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedHostRules(Notification::TypeInstance, host->GetName())) {
+ if (EvaluateApplyRule(host, *rule, true))
+ rule->AddMatch();
+ }
+}
+
+void Notification::EvaluateApplyRules(const Service::Ptr& service)
+{
+ CONTEXT("Evaluating 'apply' rules for service '" << service->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(Notification::TypeInstance, Service::TypeInstance)) {
+ if (EvaluateApplyRule(service, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedServiceRules(Notification::TypeInstance, service->GetHost()->GetName(), service->GetShortName())) {
+ if (EvaluateApplyRule(service, *rule, true))
+ rule->AddMatch();
+ }
+}
diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp
new file mode 100644
index 0000000..ab8d42b
--- /dev/null
+++ b/lib/icinga/notification.cpp
@@ -0,0 +1,812 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/notification.hpp"
+#include "icinga/notification-ti.cpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/service.hpp"
+#include "remote/apilistener.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/utility.hpp"
+#include "base/convert.hpp"
+#include "base/exception.hpp"
+#include "base/initialize.hpp"
+#include "base/scriptglobal.hpp"
+#include <algorithm>
+
+using namespace icinga;
+
+REGISTER_TYPE(Notification);
+INITIALIZE_ONCE(&Notification::StaticInitialize);
+
+std::map<String, int> Notification::m_StateFilterMap;
+std::map<String, int> Notification::m_TypeFilterMap;
+
+boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> Notification::OnNextNotificationChanged;
+boost::signals2::signal<void (const Notification::Ptr&, const String&, uint_fast8_t, const MessageOrigin::Ptr&)> Notification::OnLastNotifiedStatePerUserUpdated;
+boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> Notification::OnLastNotifiedStatePerUserCleared;
+
+String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ Notification::Ptr notification = dynamic_pointer_cast<Notification>(context);
+
+ if (!notification)
+ return "";
+
+ String name = notification->GetHostName();
+
+ if (!notification->GetServiceName().IsEmpty())
+ name += "!" + notification->GetServiceName();
+
+ name += "!" + shortName;
+
+ return name;
+}
+
+Dictionary::Ptr NotificationNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Notification name."));
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("host_name", tokens[0]);
+
+ if (tokens.size() > 2) {
+ result->Set("service_name", tokens[1]);
+ result->Set("name", tokens[2]);
+ } else {
+ result->Set("name", tokens[1]);
+ }
+
+ return result;
+}
+
+void Notification::StaticInitialize()
+{
+ ScriptGlobal::Set("Icinga.OK", "OK");
+ ScriptGlobal::Set("Icinga.Warning", "Warning");
+ ScriptGlobal::Set("Icinga.Critical", "Critical");
+ ScriptGlobal::Set("Icinga.Unknown", "Unknown");
+ ScriptGlobal::Set("Icinga.Up", "Up");
+ ScriptGlobal::Set("Icinga.Down", "Down");
+
+ ScriptGlobal::Set("Icinga.DowntimeStart", "DowntimeStart");
+ ScriptGlobal::Set("Icinga.DowntimeEnd", "DowntimeEnd");
+ ScriptGlobal::Set("Icinga.DowntimeRemoved", "DowntimeRemoved");
+ ScriptGlobal::Set("Icinga.Custom", "Custom");
+ ScriptGlobal::Set("Icinga.Acknowledgement", "Acknowledgement");
+ ScriptGlobal::Set("Icinga.Problem", "Problem");
+ ScriptGlobal::Set("Icinga.Recovery", "Recovery");
+ ScriptGlobal::Set("Icinga.FlappingStart", "FlappingStart");
+ ScriptGlobal::Set("Icinga.FlappingEnd", "FlappingEnd");
+
+ m_StateFilterMap["OK"] = StateFilterOK;
+ m_StateFilterMap["Warning"] = StateFilterWarning;
+ m_StateFilterMap["Critical"] = StateFilterCritical;
+ m_StateFilterMap["Unknown"] = StateFilterUnknown;
+ m_StateFilterMap["Up"] = StateFilterUp;
+ m_StateFilterMap["Down"] = StateFilterDown;
+
+ m_TypeFilterMap["DowntimeStart"] = NotificationDowntimeStart;
+ m_TypeFilterMap["DowntimeEnd"] = NotificationDowntimeEnd;
+ m_TypeFilterMap["DowntimeRemoved"] = NotificationDowntimeRemoved;
+ m_TypeFilterMap["Custom"] = NotificationCustom;
+ m_TypeFilterMap["Acknowledgement"] = NotificationAcknowledgement;
+ m_TypeFilterMap["Problem"] = NotificationProblem;
+ m_TypeFilterMap["Recovery"] = NotificationRecovery;
+ m_TypeFilterMap["FlappingStart"] = NotificationFlappingStart;
+ m_TypeFilterMap["FlappingEnd"] = NotificationFlappingEnd;
+}
+
+void Notification::OnConfigLoaded()
+{
+ ObjectImpl<Notification>::OnConfigLoaded();
+
+ SetTypeFilter(FilterArrayToInt(GetTypes(), GetTypeFilterMap(), ~0));
+ SetStateFilter(FilterArrayToInt(GetStates(), GetStateFilterMap(), ~0));
+}
+
+void Notification::OnAllConfigLoaded()
+{
+ ObjectImpl<Notification>::OnAllConfigLoaded();
+
+ Host::Ptr host = Host::GetByName(GetHostName());
+
+ if (GetServiceName().IsEmpty())
+ m_Checkable = host;
+ else
+ m_Checkable = host->GetServiceByShortName(GetServiceName());
+
+ if (!m_Checkable)
+ BOOST_THROW_EXCEPTION(ScriptError("Notification object refers to a host/service which doesn't exist.", GetDebugInfo()));
+
+ GetCheckable()->RegisterNotification(this);
+}
+
+void Notification::Start(bool runtimeCreated)
+{
+ Checkable::Ptr obj = GetCheckable();
+
+ if (obj)
+ obj->RegisterNotification(this);
+
+ if (ApiListener::IsHACluster() && GetNextNotification() < Utility::GetTime() + 60)
+ SetNextNotification(Utility::GetTime() + 60, true);
+
+ for (const UserGroup::Ptr& group : GetUserGroups())
+ group->AddNotification(this);
+
+ ObjectImpl<Notification>::Start(runtimeCreated);
+}
+
+void Notification::Stop(bool runtimeRemoved)
+{
+ ObjectImpl<Notification>::Stop(runtimeRemoved);
+
+ Checkable::Ptr obj = GetCheckable();
+
+ if (obj)
+ obj->UnregisterNotification(this);
+
+ for (const UserGroup::Ptr& group : GetUserGroups())
+ group->RemoveNotification(this);
+}
+
+Checkable::Ptr Notification::GetCheckable() const
+{
+ return static_pointer_cast<Checkable>(m_Checkable);
+}
+
+NotificationCommand::Ptr Notification::GetCommand() const
+{
+ return NotificationCommand::GetByName(GetCommandRaw());
+}
+
+std::set<User::Ptr> Notification::GetUsers() const
+{
+ std::set<User::Ptr> result;
+
+ Array::Ptr users = GetUsersRaw();
+
+ if (users) {
+ ObjectLock olock(users);
+
+ for (const String& name : users) {
+ User::Ptr user = User::GetByName(name);
+
+ if (!user)
+ continue;
+
+ result.insert(user);
+ }
+ }
+
+ return result;
+}
+
+std::set<UserGroup::Ptr> Notification::GetUserGroups() const
+{
+ std::set<UserGroup::Ptr> result;
+
+ Array::Ptr groups = GetUserGroupsRaw();
+
+ if (groups) {
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ UserGroup::Ptr ug = UserGroup::GetByName(name);
+
+ if (!ug)
+ continue;
+
+ result.insert(ug);
+ }
+ }
+
+ return result;
+}
+
+TimePeriod::Ptr Notification::GetPeriod() const
+{
+ return TimePeriod::GetByName(GetPeriodRaw());
+}
+
+void Notification::UpdateNotificationNumber()
+{
+ SetNotificationNumber(GetNotificationNumber() + 1);
+}
+
+void Notification::ResetNotificationNumber()
+{
+ SetNotificationNumber(0);
+}
+
+void Notification::BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force, bool reminder, const String& author, const String& text)
+{
+ String notificationName = GetName();
+ String notificationTypeName = NotificationTypeToString(type);
+
+ Log(LogNotice, "Notification")
+ << "Attempting to send " << (reminder ? "reminder " : "")
+ << "notifications of type '" << notificationTypeName
+ << "' for notification object '" << notificationName << "'.";
+
+ if (type == NotificationRecovery) {
+ auto states (GetLastNotifiedStatePerUser());
+
+ states->Clear();
+ OnLastNotifiedStatePerUserCleared(this, nullptr);
+ }
+
+ Checkable::Ptr checkable = GetCheckable();
+
+ if (!force) {
+ TimePeriod::Ptr tp = GetPeriod();
+
+ if (tp && !tp->IsInside(Utility::GetTime())) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '" << notificationName
+ << "': not in timeperiod '" << tp->GetName() << "'";
+
+ if (!reminder) {
+ switch (type) {
+ case NotificationProblem:
+ case NotificationRecovery:
+ case NotificationFlappingStart:
+ case NotificationFlappingEnd:
+ {
+ /* If a non-reminder notification was suppressed, but just because of its time period,
+ * stash it into a notification types bitmask for maybe re-sending later.
+ */
+
+ ObjectLock olock (this);
+ int suppressedTypesBefore (GetSuppressedNotifications());
+ int suppressedTypesAfter (suppressedTypesBefore | type);
+
+ for (int conflict : {NotificationProblem | NotificationRecovery, NotificationFlappingStart | NotificationFlappingEnd}) {
+ /* E.g. problem and recovery notifications neutralize each other. */
+
+ if ((suppressedTypesAfter & conflict) == conflict) {
+ suppressedTypesAfter &= ~conflict;
+ }
+ }
+
+ if (suppressedTypesAfter != suppressedTypesBefore) {
+ SetSuppressedNotifications(suppressedTypesAfter);
+ }
+ }
+ default:
+ ; // Cheating the compiler on "5 enumeration values not handled in switch"
+ }
+ }
+
+ return;
+ }
+
+ double now = Utility::GetTime();
+ Dictionary::Ptr times = GetTimes();
+
+ if (times && type == NotificationProblem) {
+ Value timesBegin = times->Get("begin");
+ Value timesEnd = times->Get("end");
+
+ if (timesBegin != Empty && timesBegin >= 0 && now < checkable->GetLastHardStateChange() + timesBegin) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
+ << notificationName << "': before specified begin time (" << Utility::FormatDuration(timesBegin) << ")";
+
+ /* we need to adjust the next notification time
+ * delaying the first notification
+ */
+ SetNextNotification(checkable->GetLastHardStateChange() + timesBegin + 1.0);
+
+ /*
+ * We need to set no more notifications to false, in case
+ * some notifications were sent previously
+ */
+ SetNoMoreNotifications(false);
+
+ return;
+ }
+
+ if (timesEnd != Empty && timesEnd >= 0 && now > checkable->GetLastHardStateChange() + timesEnd) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
+ << notificationName << "': after specified end time (" << Utility::FormatDuration(timesEnd) << ")";
+ return;
+ }
+ }
+
+ unsigned long ftype = type;
+
+ Log(LogDebug, "Notification")
+ << "Type '" << NotificationTypeToString(type)
+ << "', TypeFilter: " << NotificationFilterToString(GetTypeFilter(), GetTypeFilterMap())
+ << " (FType=" << ftype << ", TypeFilter=" << GetTypeFilter() << ")";
+
+ if (!(ftype & GetTypeFilter())) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
+ << notificationName << "': type '"
+ << NotificationTypeToString(type) << "' does not match type filter: "
+ << NotificationFilterToString(GetTypeFilter(), GetTypeFilterMap()) << ".";
+
+ /* Ensure to reset no_more_notifications on Recovery notifications,
+ * even if the admin did not configure them in the filter.
+ */
+ {
+ ObjectLock olock(this);
+ if (type == NotificationRecovery && GetInterval() <= 0)
+ SetNoMoreNotifications(false);
+ }
+
+ return;
+ }
+
+ /* Check state filters for problem notifications. Recovery notifications will be filtered away later. */
+ if (type == NotificationProblem) {
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ unsigned long fstate;
+ String stateStr;
+
+ if (service) {
+ fstate = ServiceStateToFilter(service->GetState());
+ stateStr = NotificationServiceStateToString(service->GetState());
+ } else {
+ fstate = HostStateToFilter(host->GetState());
+ stateStr = NotificationHostStateToString(host->GetState());
+ }
+
+ Log(LogDebug, "Notification")
+ << "State '" << stateStr << "', StateFilter: " << NotificationFilterToString(GetStateFilter(), GetStateFilterMap())
+ << " (FState=" << fstate << ", StateFilter=" << GetStateFilter() << ")";
+
+ if (!(fstate & GetStateFilter())) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
+ << notificationName << "': state '" << stateStr
+ << "' does not match state filter: " << NotificationFilterToString(GetStateFilter(), GetStateFilterMap()) << ".";
+ return;
+ }
+ }
+ } else {
+ Log(LogNotice, "Notification")
+ << "Not checking " << (reminder ? "reminder " : "") << "notification filters for notification object '"
+ << notificationName << "': Notification was forced.";
+ }
+
+ {
+ ObjectLock olock(this);
+
+ UpdateNotificationNumber();
+ double now = Utility::GetTime();
+ SetLastNotification(now);
+
+ if (type == NotificationProblem && GetInterval() <= 0)
+ SetNoMoreNotifications(true);
+ else
+ SetNoMoreNotifications(false);
+
+ if (type == NotificationProblem && GetInterval() > 0)
+ SetNextNotification(now + GetInterval());
+
+ if (type == NotificationProblem)
+ SetLastProblemNotification(now);
+ }
+
+ std::set<User::Ptr> allUsers;
+
+ std::set<User::Ptr> users = GetUsers();
+ std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
+
+ for (const UserGroup::Ptr& ug : GetUserGroups()) {
+ std::set<User::Ptr> members = ug->GetMembers();
+ std::copy(members.begin(), members.end(), std::inserter(allUsers, allUsers.begin()));
+ }
+
+ std::set<User::Ptr> allNotifiedUsers;
+ Array::Ptr notifiedProblemUsers = GetNotifiedProblemUsers();
+
+ for (const User::Ptr& user : allUsers) {
+ String userName = user->GetName();
+
+ if (!user->GetEnableNotifications()) {
+ Log(LogNotice, "Notification")
+ << "Notification object '" << notificationName << "': Disabled notifications for user '"
+ << userName << "'. Not sending notification.";
+ continue;
+ }
+
+ if (!CheckNotificationUserFilters(type, user, force, reminder)) {
+ Log(LogNotice, "Notification")
+ << "Notification object '" << notificationName << "': Filters for user '" << userName << "' not matched. Not sending notification.";
+ continue;
+ }
+
+ /* on recovery, check if user was notified before */
+ if (type == NotificationRecovery) {
+ if (!notifiedProblemUsers->Contains(userName) && (NotificationProblem & user->GetTypeFilter())) {
+ Log(LogNotice, "Notification")
+ << "Notification object '" << notificationName << "': We did not notify user '" << userName
+ << "' (Problem types enabled) for a problem before. Not sending Recovery notification.";
+ continue;
+ }
+ }
+
+ /* on acknowledgement, check if user was notified before */
+ if (type == NotificationAcknowledgement) {
+ if (!notifiedProblemUsers->Contains(userName) && (NotificationProblem & user->GetTypeFilter())) {
+ Log(LogNotice, "Notification")
+ << "Notification object '" << notificationName << "': We did not notify user '" << userName
+ << "' (Problem types enabled) for a problem before. Not sending acknowledgement notification.";
+ continue;
+ }
+ }
+
+ if (type == NotificationProblem && !reminder && !checkable->GetVolatile()) {
+ auto [host, service] = GetHostService(checkable);
+ uint_fast8_t state = service ? service->GetState() : host->GetState();
+
+ if (state == (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) {
+ auto stateStr (service ? NotificationServiceStateToString(service->GetState()) : NotificationHostStateToString(host->GetState()));
+
+ Log(LogNotice, "Notification")
+ << "Notification object '" << notificationName << "': We already notified user '" << userName << "' for a " << stateStr
+ << " problem. Likely after that another state change notification was filtered out by config. Not sending duplicate '"
+ << stateStr << "' notification.";
+
+ continue;
+ }
+ }
+
+ Log(LogInformation, "Notification")
+ << "Sending " << (reminder ? "reminder " : "") << "'" << NotificationTypeToString(type) << "' notification '"
+ << notificationName << "' for user '" << userName << "'";
+
+ // Explicitly use Notification::Ptr to keep the reference counted while the callback is active
+ Notification::Ptr notification (this);
+ Utility::QueueAsyncCallback([notification, type, user, cr, force, author, text]() {
+ notification->ExecuteNotificationHelper(type, user, cr, force, author, text);
+ });
+
+ /* collect all notified users */
+ allNotifiedUsers.insert(user);
+
+ if (type == NotificationProblem) {
+ auto [host, service] = GetHostService(checkable);
+ uint_fast8_t state = service ? service->GetState() : host->GetState();
+
+ if (state != (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) {
+ GetLastNotifiedStatePerUser()->Set(userName, state);
+ OnLastNotifiedStatePerUserUpdated(this, userName, state, nullptr);
+ }
+ }
+
+ /* store all notified users for later recovery checks */
+ if (type == NotificationProblem && !notifiedProblemUsers->Contains(userName))
+ notifiedProblemUsers->Add(userName);
+ }
+
+ /* if this was a recovery notification, reset all notified users */
+ if (type == NotificationRecovery)
+ notifiedProblemUsers->Clear();
+
+ /* used in db_ido for notification history */
+ Service::OnNotificationSentToAllUsers(this, checkable, allNotifiedUsers, type, cr, author, text, nullptr);
+}
+
+bool Notification::CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force, bool reminder)
+{
+ String notificationName = GetName();
+ String userName = user->GetName();
+
+ if (!force) {
+ TimePeriod::Ptr tp = user->GetPeriod();
+
+ if (tp && !tp->IsInside(Utility::GetTime())) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
+ << notificationName << " and user '" << userName
+ << "': user period not in timeperiod '" << tp->GetName() << "'";
+ return false;
+ }
+
+ unsigned long ftype = type;
+
+ Log(LogDebug, "Notification")
+ << "User '" << userName << "' notification '" << notificationName
+ << "', Type '" << NotificationTypeToString(type)
+ << "', TypeFilter: " << NotificationFilterToString(user->GetTypeFilter(), GetTypeFilterMap())
+ << " (FType=" << ftype << ", TypeFilter=" << GetTypeFilter() << ")";
+
+
+ if (!(ftype & user->GetTypeFilter())) {
+ Log(LogNotice, "Notification")
+ << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
+ << notificationName << " and user '" << userName << "': type '"
+ << NotificationTypeToString(type) << "' does not match type filter: "
+ << NotificationFilterToString(user->GetTypeFilter(), GetTypeFilterMap()) << ".";
+ return false;
+ }
+
+ /* check state filters it this is not a recovery notification */
+ if (type != NotificationRecovery) {
+ Checkable::Ptr checkable = GetCheckable();
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ unsigned long fstate;
+ String stateStr;
+
+ if (service) {
+ fstate = ServiceStateToFilter(service->GetState());
+ stateStr = NotificationServiceStateToString(service->GetState());
+ } else {
+ fstate = HostStateToFilter(host->GetState());
+ stateStr = NotificationHostStateToString(host->GetState());
+ }
+
+ Log(LogDebug, "Notification")
+ << "User '" << userName << "' notification '" << notificationName
+ << "', State '" << stateStr << "', StateFilter: "
+ << NotificationFilterToString(user->GetStateFilter(), GetStateFilterMap())
+ << " (FState=" << fstate << ", StateFilter=" << user->GetStateFilter() << ")";
+
+ if (!(fstate & user->GetStateFilter())) {
+ Log(LogNotice, "Notification")
+ << "Not " << (reminder ? "reminder " : "") << "sending notifications for notification object '"
+ << notificationName << " and user '" << userName << "': state '" << stateStr
+ << "' does not match state filter: " << NotificationFilterToString(user->GetStateFilter(), GetStateFilterMap()) << ".";
+ return false;
+ }
+ }
+ } else {
+ Log(LogNotice, "Notification")
+ << "Not checking " << (reminder ? "reminder " : "") << "notification filters for notification object '"
+ << notificationName << "' and user '" << userName << "': Notification was forced.";
+ }
+
+ return true;
+}
+
+void Notification::ExecuteNotificationHelper(NotificationType type, const User::Ptr& user, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
+{
+ String notificationName = GetName();
+ String userName = user->GetName();
+ String checkableName = GetCheckable()->GetName();
+
+ NotificationCommand::Ptr command = GetCommand();
+
+ if (!command) {
+ Log(LogDebug, "Notification")
+ << "No command found for notification '" << notificationName << "'. Skipping execution.";
+ return;
+ }
+
+ String commandName = command->GetName();
+
+ try {
+ command->Execute(this, user, cr, type, author, text);
+
+ /* required by compatlogger */
+ Service::OnNotificationSentToUser(this, GetCheckable(), user, type, cr, author, text, commandName, nullptr);
+
+ Log(LogInformation, "Notification")
+ << "Completed sending '" << NotificationTypeToString(type)
+ << "' notification '" << notificationName
+ << "' for checkable '" << checkableName
+ << "' and user '" << userName << "' using command '" << commandName << "'.";
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "Notification")
+ << "Exception occurred during notification '" << notificationName
+ << "' for checkable '" << checkableName
+ << "' and user '" << userName << "' using command '" << commandName << "': "
+ << DiagnosticInformation(ex, false);
+ }
+}
+
+int icinga::ServiceStateToFilter(ServiceState state)
+{
+ switch (state) {
+ case ServiceOK:
+ return StateFilterOK;
+ case ServiceWarning:
+ return StateFilterWarning;
+ case ServiceCritical:
+ return StateFilterCritical;
+ case ServiceUnknown:
+ return StateFilterUnknown;
+ default:
+ VERIFY(!"Invalid state type.");
+ }
+}
+
+int icinga::HostStateToFilter(HostState state)
+{
+ switch (state) {
+ case HostUp:
+ return StateFilterUp;
+ case HostDown:
+ return StateFilterDown;
+ default:
+ VERIFY(!"Invalid state type.");
+ }
+}
+
+String Notification::NotificationFilterToString(int filter, const std::map<String, int>& filterMap)
+{
+ std::vector<String> sFilters;
+
+ typedef std::pair<String, int> kv_pair;
+ for (const kv_pair& kv : filterMap) {
+ if (filter & kv.second)
+ sFilters.push_back(kv.first);
+ }
+
+ return Utility::NaturalJoin(sFilters);
+}
+
+/*
+ * Main interface to translate NotificationType values into strings.
+ */
+String Notification::NotificationTypeToString(NotificationType type)
+{
+ auto typeMap = Notification::m_TypeFilterMap;
+
+ auto it = std::find_if(typeMap.begin(), typeMap.end(),
+ [&type](const std::pair<String, int>& p) {
+ return p.second == type;
+ });
+
+ if (it == typeMap.end())
+ return Empty;
+
+ return it->first;
+}
+
+
+/*
+ * Compat interface used in external features.
+ */
+String Notification::NotificationTypeToStringCompat(NotificationType type)
+{
+ switch (type) {
+ case NotificationDowntimeStart:
+ return "DOWNTIMESTART";
+ case NotificationDowntimeEnd:
+ return "DOWNTIMEEND";
+ case NotificationDowntimeRemoved:
+ return "DOWNTIMECANCELLED";
+ case NotificationCustom:
+ return "CUSTOM";
+ case NotificationAcknowledgement:
+ return "ACKNOWLEDGEMENT";
+ case NotificationProblem:
+ return "PROBLEM";
+ case NotificationRecovery:
+ return "RECOVERY";
+ case NotificationFlappingStart:
+ return "FLAPPINGSTART";
+ case NotificationFlappingEnd:
+ return "FLAPPINGEND";
+ default:
+ return "UNKNOWN_NOTIFICATION";
+ }
+}
+
+String Notification::NotificationServiceStateToString(ServiceState state)
+{
+ switch (state) {
+ case ServiceOK:
+ return "OK";
+ case ServiceWarning:
+ return "Warning";
+ case ServiceCritical:
+ return "Critical";
+ case ServiceUnknown:
+ return "Unknown";
+ default:
+ VERIFY(!"Invalid state type.");
+ }
+}
+
+String Notification::NotificationHostStateToString(HostState state)
+{
+ switch (state) {
+ case HostUp:
+ return "Up";
+ case HostDown:
+ return "Down";
+ default:
+ VERIFY(!"Invalid state type.");
+ }
+}
+
+void Notification::Validate(int types, const ValidationUtils& utils)
+{
+ ObjectImpl<Notification>::Validate(types, utils);
+
+ if (!(types & FAConfig))
+ return;
+
+ Array::Ptr users = GetUsersRaw();
+ Array::Ptr groups = GetUserGroupsRaw();
+
+ if ((!users || users->GetLength() == 0) && (!groups || groups->GetLength() == 0))
+ BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(), "Validation failed: No users/user_groups specified."));
+}
+
+void Notification::ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Notification>::ValidateStates(lvalue, utils);
+
+ int filter = FilterArrayToInt(lvalue(), GetStateFilterMap(), 0);
+
+ if (GetServiceName().IsEmpty() && (filter == -1 || (filter & ~(StateFilterUp | StateFilterDown)) != 0))
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid."));
+
+ if (!GetServiceName().IsEmpty() && (filter == -1 || (filter & ~(StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0))
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid."));
+}
+
+void Notification::ValidateTypes(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Notification>::ValidateTypes(lvalue, utils);
+
+ int filter = FilterArrayToInt(lvalue(), GetTypeFilterMap(), 0);
+
+ if (filter == -1 || (filter & ~(NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved |
+ NotificationCustom | NotificationAcknowledgement | NotificationProblem | NotificationRecovery |
+ NotificationFlappingStart | NotificationFlappingEnd)) != 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "types" }, "Type filter is invalid."));
+}
+
+void Notification::ValidateTimes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Notification>::ValidateTimes(lvalue, utils);
+
+ Dictionary::Ptr times = lvalue();
+
+ if (!times)
+ return;
+
+ double begin;
+ double end;
+
+ try {
+ begin = Convert::ToDouble(times->Get("begin"));
+ } catch (const std::exception&) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "times" }, "'begin' is invalid, must be duration or number." ));
+ }
+
+ try {
+ end = Convert::ToDouble(times->Get("end"));
+ } catch (const std::exception&) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "times" }, "'end' is invalid, must be duration or number." ));
+ }
+
+ /* Also solve logical errors where begin > end. */
+ if (begin > 0 && end > 0 && begin > end)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "times" }, "'begin' must be smaller than 'end'."));
+}
+
+Endpoint::Ptr Notification::GetCommandEndpoint() const
+{
+ return Endpoint::GetByName(GetCommandEndpointRaw());
+}
+
+const std::map<String, int>& Notification::GetStateFilterMap()
+{
+ return m_StateFilterMap;
+}
+
+const std::map<String, int>& Notification::GetTypeFilterMap()
+{
+ return m_TypeFilterMap;
+}
diff --git a/lib/icinga/notification.hpp b/lib/icinga/notification.hpp
new file mode 100644
index 0000000..1b6cbed
--- /dev/null
+++ b/lib/icinga/notification.hpp
@@ -0,0 +1,135 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef NOTIFICATION_H
+#define NOTIFICATION_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/notification-ti.hpp"
+#include "icinga/checkable-ti.hpp"
+#include "icinga/user.hpp"
+#include "icinga/usergroup.hpp"
+#include "icinga/timeperiod.hpp"
+#include "icinga/checkresult.hpp"
+#include "remote/endpoint.hpp"
+#include "remote/messageorigin.hpp"
+#include "base/array.hpp"
+#include <cstdint>
+
+namespace icinga
+{
+
+/**
+ * @ingroup icinga
+ */
+enum NotificationFilter
+{
+ StateFilterOK = 1,
+ StateFilterWarning = 2,
+ StateFilterCritical = 4,
+ StateFilterUnknown = 8,
+
+ StateFilterUp = 16,
+ StateFilterDown = 32
+};
+
+/**
+ * The notification type.
+ *
+ * @ingroup icinga
+ */
+enum NotificationType
+{
+ NotificationDowntimeStart = 1,
+ NotificationDowntimeEnd = 2,
+ NotificationDowntimeRemoved = 4,
+ NotificationCustom = 8,
+ NotificationAcknowledgement = 16,
+ NotificationProblem = 32,
+ NotificationRecovery = 64,
+ NotificationFlappingStart = 128,
+ NotificationFlappingEnd = 256
+};
+
+class NotificationCommand;
+class ApplyRule;
+struct ScriptFrame;
+class Host;
+class Service;
+
+/**
+ * An Icinga notification specification.
+ *
+ * @ingroup icinga
+ */
+class Notification final : public ObjectImpl<Notification>
+{
+public:
+ DECLARE_OBJECT(Notification);
+ DECLARE_OBJECTNAME(Notification);
+
+ static void StaticInitialize();
+
+ intrusive_ptr<Checkable> GetCheckable() const;
+ intrusive_ptr<NotificationCommand> GetCommand() const;
+ TimePeriod::Ptr GetPeriod() const;
+ std::set<User::Ptr> GetUsers() const;
+ std::set<UserGroup::Ptr> GetUserGroups() const;
+
+ void UpdateNotificationNumber();
+ void ResetNotificationNumber();
+
+ void BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force,
+ bool reminder = false, const String& author = "", const String& text = "");
+
+ Endpoint::Ptr GetCommandEndpoint() const;
+
+ // Logging, etc.
+ static String NotificationTypeToString(NotificationType type);
+ // Compat, used for notifications, etc.
+ static String NotificationTypeToStringCompat(NotificationType type);
+ static String NotificationFilterToString(int filter, const std::map<String, int>& filterMap);
+
+ static String NotificationServiceStateToString(ServiceState state);
+ static String NotificationHostStateToString(HostState state);
+
+ static boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> OnNextNotificationChanged;
+ static boost::signals2::signal<void (const Notification::Ptr&, const String&, uint_fast8_t, const MessageOrigin::Ptr&)> OnLastNotifiedStatePerUserUpdated;
+ static boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> OnLastNotifiedStatePerUserCleared;
+
+ void Validate(int types, const ValidationUtils& utils) override;
+
+ void ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
+ void ValidateTypes(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
+ void ValidateTimes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
+
+ static void EvaluateApplyRules(const intrusive_ptr<Host>& host);
+ static void EvaluateApplyRules(const intrusive_ptr<Service>& service);
+
+ static const std::map<String, int>& GetStateFilterMap();
+ static const std::map<String, int>& GetTypeFilterMap();
+
+ void OnConfigLoaded() override;
+ void OnAllConfigLoaded() override;
+ void Start(bool runtimeCreated) override;
+ void Stop(bool runtimeRemoved) override;
+
+private:
+ ObjectImpl<Checkable>::Ptr m_Checkable;
+
+ bool CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force, bool reminder);
+
+ void ExecuteNotificationHelper(NotificationType type, const User::Ptr& user, const CheckResult::Ptr& cr, bool force, const String& author = "", const String& text = "");
+
+ static bool EvaluateApplyRuleInstance(const intrusive_ptr<Checkable>& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter);
+ static bool EvaluateApplyRule(const intrusive_ptr<Checkable>& checkable, const ApplyRule& rule, bool skipFilter = false);
+
+ static std::map<String, int> m_StateFilterMap;
+ static std::map<String, int> m_TypeFilterMap;
+};
+
+int ServiceStateToFilter(ServiceState state);
+int HostStateToFilter(HostState state);
+
+}
+
+#endif /* NOTIFICATION_H */
diff --git a/lib/icinga/notification.ti b/lib/icinga/notification.ti
new file mode 100644
index 0000000..be07846
--- /dev/null
+++ b/lib/icinga/notification.ti
@@ -0,0 +1,111 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#impl_include "icinga/notificationcommand.hpp"
+#impl_include "icinga/service.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+class NotificationNameComposer : public NameComposer
+{
+public:
+ virtual String MakeName(const String& shortName, const Object::Ptr& context) const;
+ virtual Dictionary::Ptr ParseName(const String& name) const;
+};
+}}}
+
+class Notification : CustomVarObject < NotificationNameComposer
+{
+ load_after Host;
+ load_after Service;
+
+ [config, protected, required, navigation] name(NotificationCommand) command (CommandRaw) {
+ navigate {{{
+ return NotificationCommand::GetByName(GetCommandRaw());
+ }}}
+ };
+ [config] double interval {
+ default {{{ return 1800; }}}
+ };
+ [config, navigation] name(TimePeriod) period (PeriodRaw) {
+ navigate {{{
+ return TimePeriod::GetByName(GetPeriodRaw());
+ }}}
+ };
+ [config, signal_with_old_value] array(name(User)) users (UsersRaw);
+ [config, signal_with_old_value] array(name(UserGroup)) user_groups (UserGroupsRaw);
+ [config] Dictionary::Ptr times;
+ [config] array(Value) types;
+ [no_user_view, no_user_modify] int type_filter_real (TypeFilter);
+ [config] array(Value) states;
+ [no_user_view, no_user_modify] int state_filter_real (StateFilter);
+ [config, no_user_modify, protected, required, navigation(host)] name(Host) host_name {
+ navigate {{{
+ return Host::GetByName(GetHostName());
+ }}}
+ };
+ [config, protected, no_user_modify, navigation(service)] String service_name {
+ track {{{
+ if (!oldValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), oldValue);
+ DependencyGraph::RemoveDependency(this, service.get());
+ }
+
+ if (!newValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), newValue);
+ DependencyGraph::AddDependency(this, service.get());
+ }
+ }}}
+ navigate {{{
+ if (GetServiceName().IsEmpty())
+ return nullptr;
+
+ Host::Ptr host = Host::GetByName(GetHostName());
+ return host->GetServiceByShortName(GetServiceName());
+ }}}
+ };
+
+ [state, no_user_modify] Array::Ptr notified_problem_users {
+ default {{{ return new Array(); }}}
+ };
+
+ [state, no_user_modify] bool no_more_notifications {
+ default {{{ return false; }}}
+ };
+
+ [state, no_user_view, no_user_modify] Array::Ptr stashed_notifications {
+ default {{{ return new Array(); }}}
+ };
+
+ [state] Timestamp last_notification;
+ [state] Timestamp next_notification;
+ [state] int notification_number;
+ [state] Timestamp last_problem_notification;
+
+ [state, no_user_view, no_user_modify] int suppressed_notifications {
+ default {{{ return 0; }}}
+ };
+
+ [state, no_user_view, no_user_modify] Dictionary::Ptr last_notified_state_per_user {
+ default {{{ return new Dictionary(); }}}
+ };
+
+ [config, navigation] name(Endpoint) command_endpoint (CommandEndpointRaw) {
+ navigate {{{
+ return Endpoint::GetByName(GetCommandEndpointRaw());
+ }}}
+ };
+};
+
+validator Notification {
+ Dictionary times {
+ Number begin;
+ Number end;
+ };
+};
+
+}
diff --git a/lib/icinga/notificationcommand.cpp b/lib/icinga/notificationcommand.cpp
new file mode 100644
index 0000000..d4a5fd6
--- /dev/null
+++ b/lib/icinga/notificationcommand.cpp
@@ -0,0 +1,27 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/notificationcommand.hpp"
+#include "icinga/notificationcommand-ti.cpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(NotificationCommand);
+
+thread_local NotificationCommand::Ptr NotificationCommand::ExecuteOverride;
+
+Dictionary::Ptr NotificationCommand::Execute(const Notification::Ptr& notification,
+ const User::Ptr& user, const CheckResult::Ptr& cr, const NotificationType& type,
+ const String& author, const String& comment, const Dictionary::Ptr& resolvedMacros,
+ bool useResolvedMacros)
+{
+ return GetExecute()->Invoke({
+ notification,
+ user,
+ cr,
+ type,
+ author,
+ comment,
+ resolvedMacros,
+ useResolvedMacros,
+ });
+}
diff --git a/lib/icinga/notificationcommand.hpp b/lib/icinga/notificationcommand.hpp
new file mode 100644
index 0000000..f0f6899
--- /dev/null
+++ b/lib/icinga/notificationcommand.hpp
@@ -0,0 +1,36 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef NOTIFICATIONCOMMAND_H
+#define NOTIFICATIONCOMMAND_H
+
+#include "icinga/notificationcommand-ti.hpp"
+#include "icinga/notification.hpp"
+
+namespace icinga
+{
+
+class Notification;
+
+/**
+ * A notification command.
+ *
+ * @ingroup icinga
+ */
+class NotificationCommand final : public ObjectImpl<NotificationCommand>
+{
+public:
+ DECLARE_OBJECT(NotificationCommand);
+ DECLARE_OBJECTNAME(NotificationCommand);
+
+ static thread_local NotificationCommand::Ptr ExecuteOverride;
+
+ virtual Dictionary::Ptr Execute(const intrusive_ptr<Notification>& notification,
+ const User::Ptr& user, const CheckResult::Ptr& cr, const NotificationType& type,
+ const String& author, const String& comment,
+ const Dictionary::Ptr& resolvedMacros = nullptr,
+ bool useResolvedMacros = false);
+};
+
+}
+
+#endif /* NOTIFICATIONCOMMAND_H */
diff --git a/lib/icinga/notificationcommand.ti b/lib/icinga/notificationcommand.ti
new file mode 100644
index 0000000..51207a3
--- /dev/null
+++ b/lib/icinga/notificationcommand.ti
@@ -0,0 +1,14 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/command.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class NotificationCommand : Command
+{
+};
+
+}
diff --git a/lib/icinga/objectutils.cpp b/lib/icinga/objectutils.cpp
new file mode 100644
index 0000000..559ca43
--- /dev/null
+++ b/lib/icinga/objectutils.cpp
@@ -0,0 +1,55 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/objectutils.hpp"
+#include "icinga/host.hpp"
+#include "icinga/user.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/eventcommand.hpp"
+#include "icinga/notificationcommand.hpp"
+#include "icinga/hostgroup.hpp"
+#include "icinga/servicegroup.hpp"
+#include "icinga/usergroup.hpp"
+
+using namespace icinga;
+
+REGISTER_FUNCTION(Icinga, get_host, &Host::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_service, &ObjectUtils::GetService, "host:name");
+REGISTER_FUNCTION(Icinga, get_services, &ObjectUtils::GetServices, "host");
+REGISTER_FUNCTION(Icinga, get_user, &User::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_check_command, &CheckCommand::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_event_command, &EventCommand::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_notification_command, &NotificationCommand::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_host_group, &HostGroup::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_service_group, &ServiceGroup::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_user_group, &UserGroup::GetByName, "name");
+REGISTER_FUNCTION(Icinga, get_time_period, &TimePeriod::GetByName, "name");
+
+Service::Ptr ObjectUtils::GetService(const Value& host, const String& name)
+{
+ Host::Ptr hostObj;
+
+ if (host.IsObjectType<Host>())
+ hostObj = host;
+ else
+ hostObj = Host::GetByName(host);
+
+ if (!hostObj)
+ return nullptr;
+
+ return hostObj->GetServiceByShortName(name);
+}
+
+Array::Ptr ObjectUtils::GetServices(const Value& host)
+{
+ Host::Ptr hostObj;
+
+ if (host.IsObjectType<Host>())
+ hostObj = host;
+ else
+ hostObj = Host::GetByName(host);
+
+ if (!hostObj)
+ return nullptr;
+
+ return Array::FromVector(hostObj->GetServices());
+}
diff --git a/lib/icinga/objectutils.hpp b/lib/icinga/objectutils.hpp
new file mode 100644
index 0000000..42e2953
--- /dev/null
+++ b/lib/icinga/objectutils.hpp
@@ -0,0 +1,29 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef OBJECTUTILS_H
+#define OBJECTUTILS_H
+
+#include "base/i2-base.hpp"
+#include "base/string.hpp"
+#include "base/array.hpp"
+#include "icinga/service.hpp"
+
+namespace icinga
+{
+
+/**
+ * @ingroup icinga
+ */
+class ObjectUtils
+{
+public:
+ static Service::Ptr GetService(const Value& host, const String& name);
+ static Array::Ptr GetServices(const Value& host);
+
+private:
+ ObjectUtils();
+};
+
+}
+
+#endif /* OBJECTUTILS_H */
diff --git a/lib/icinga/pluginutility.cpp b/lib/icinga/pluginutility.cpp
new file mode 100644
index 0000000..4dc46f7
--- /dev/null
+++ b/lib/icinga/pluginutility.cpp
@@ -0,0 +1,218 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/pluginutility.hpp"
+#include "icinga/macroprocessor.hpp"
+#include "base/logger.hpp"
+#include "base/utility.hpp"
+#include "base/perfdatavalue.hpp"
+#include "base/convert.hpp"
+#include "base/process.hpp"
+#include "base/objectlock.hpp"
+#include "base/exception.hpp"
+#include <boost/algorithm/string/trim.hpp>
+
+using namespace icinga;
+
+void PluginUtility::ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable,
+ const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int timeout,
+ const std::function<void(const Value& commandLine, const ProcessResult&)>& callback)
+{
+ Value raw_command = commandObj->GetCommandLine();
+ Dictionary::Ptr raw_arguments = commandObj->GetArguments();
+
+ Value command;
+
+ try {
+ command = MacroProcessor::ResolveArguments(raw_command, raw_arguments,
+ macroResolvers, cr, resolvedMacros, useResolvedMacros);
+ } catch (const std::exception& ex) {
+ String message = DiagnosticInformation(ex);
+
+ Log(LogWarning, "PluginUtility", message);
+
+ if (callback) {
+ ProcessResult pr;
+ pr.PID = -1;
+ pr.ExecutionStart = Utility::GetTime();
+ pr.ExecutionEnd = pr.ExecutionStart;
+ pr.ExitStatus = 3; /* Unknown */
+ pr.Output = message;
+ callback(Empty, pr);
+ }
+
+ return;
+ }
+
+ Dictionary::Ptr envMacros = new Dictionary();
+
+ Dictionary::Ptr env = commandObj->GetEnv();
+
+ if (env) {
+ ObjectLock olock(env);
+ for (const Dictionary::Pair& kv : env) {
+ String name = kv.second;
+
+ String missingMacro;
+ Value value = MacroProcessor::ResolveMacros(name, macroResolvers, cr,
+ &missingMacro, MacroProcessor::EscapeCallback(), resolvedMacros,
+ useResolvedMacros);
+
+#ifdef I2_DEBUG
+ if (!missingMacro.IsEmpty())
+ Log(LogDebug, "PluginUtility")
+ << "Macro '" << name << "' is not defined.";
+#endif /* I2_DEBUG */
+
+ if (value.IsObjectType<Array>())
+ value = Utility::Join(value, ';');
+
+ envMacros->Set(kv.first, value);
+ }
+ }
+
+ if (resolvedMacros && !useResolvedMacros)
+ return;
+
+ Process::Ptr process = new Process(Process::PrepareCommand(command), envMacros);
+
+ process->SetTimeout(timeout);
+ process->SetAdjustPriority(true);
+
+ process->Run([callback, command](const ProcessResult& pr) { callback(command, pr); });
+}
+
+ServiceState PluginUtility::ExitStatusToState(int exitStatus)
+{
+ switch (exitStatus) {
+ case 0:
+ return ServiceOK;
+ case 1:
+ return ServiceWarning;
+ case 2:
+ return ServiceCritical;
+ default:
+ return ServiceUnknown;
+ }
+}
+
+std::pair<String, String> PluginUtility::ParseCheckOutput(const String& output)
+{
+ String text;
+ String perfdata;
+
+ std::vector<String> lines = output.Split("\r\n");
+
+ for (const String& line : lines) {
+ size_t delim = line.FindFirstOf("|");
+
+ if (!text.IsEmpty())
+ text += "\n";
+
+ if (delim != String::NPos && line.FindFirstOf("=", delim) != String::NPos) {
+ text += line.SubStr(0, delim);
+
+ if (!perfdata.IsEmpty())
+ perfdata += " ";
+
+ perfdata += line.SubStr(delim + 1, line.GetLength());
+ } else {
+ text += line;
+ }
+ }
+
+ boost::algorithm::trim(perfdata);
+
+ return std::make_pair(text, perfdata);
+}
+
+Array::Ptr PluginUtility::SplitPerfdata(const String& perfdata)
+{
+ ArrayData result;
+
+ size_t begin = 0;
+ String multi_prefix;
+
+ for (;;) {
+ size_t eqp = perfdata.FindFirstOf('=', begin);
+
+ if (eqp == String::NPos)
+ break;
+
+ String label = perfdata.SubStr(begin, eqp - begin);
+ boost::algorithm::trim_left(label);
+
+ if (label.GetLength() > 2 && label[0] == '\'' && label[label.GetLength() - 1] == '\'')
+ label = label.SubStr(1, label.GetLength() - 2);
+
+ size_t multi_index = label.RFind("::");
+
+ if (multi_index != String::NPos)
+ multi_prefix = "";
+
+ size_t spq = perfdata.FindFirstOf(' ', eqp);
+
+ if (spq == String::NPos)
+ spq = perfdata.GetLength();
+
+ String value = perfdata.SubStr(eqp + 1, spq - eqp - 1);
+
+ if (!multi_prefix.IsEmpty())
+ label = multi_prefix + "::" + label;
+
+ String pdv;
+ if (label.FindFirstOf(" ") != String::NPos)
+ pdv = "'" + label + "'=" + value;
+ else
+ pdv = label + "=" + value;
+
+ result.emplace_back(std::move(pdv));
+
+ if (multi_index != String::NPos)
+ multi_prefix = label.SubStr(0, multi_index);
+
+ begin = spq + 1;
+ }
+
+ return new Array(std::move(result));
+}
+
+String PluginUtility::FormatPerfdata(const Array::Ptr& perfdata, bool normalize)
+{
+ if (!perfdata)
+ return "";
+
+ std::ostringstream result;
+
+ ObjectLock olock(perfdata);
+
+ bool first = true;
+ for (const Value& pdv : perfdata) {
+ if (!first)
+ result << " ";
+ else
+ first = false;
+
+ if (pdv.IsObjectType<PerfdataValue>()) {
+ result << static_cast<PerfdataValue::Ptr>(pdv)->Format();
+ } else if (normalize) {
+ PerfdataValue::Ptr normalized;
+
+ try {
+ normalized = PerfdataValue::Parse(pdv);
+ } catch (const std::invalid_argument& ex) {
+ Log(LogDebug, "PerfdataValue") << ex.what();
+ }
+
+ if (normalized) {
+ result << normalized->Format();
+ } else {
+ result << pdv;
+ }
+ } else {
+ result << pdv;
+ }
+ }
+
+ return result.str();
+}
diff --git a/lib/icinga/pluginutility.hpp b/lib/icinga/pluginutility.hpp
new file mode 100644
index 0000000..3f6a844
--- /dev/null
+++ b/lib/icinga/pluginutility.hpp
@@ -0,0 +1,42 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef PLUGINUTILITY_H
+#define PLUGINUTILITY_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/checkable.hpp"
+#include "icinga/checkcommand.hpp"
+#include "icinga/macroprocessor.hpp"
+#include <vector>
+
+namespace icinga
+{
+
+struct ProcessResult;
+
+/**
+ * Utility functions for plugin-based checks.
+ *
+ * @ingroup icinga
+ */
+class PluginUtility
+{
+public:
+ static void ExecuteCommand(const Command::Ptr& commandObj, const Checkable::Ptr& checkable,
+ const CheckResult::Ptr& cr, const MacroProcessor::ResolverList& macroResolvers,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros, int timeout,
+ const std::function<void(const Value& commandLine, const ProcessResult&)>& callback = std::function<void(const Value& commandLine, const ProcessResult&)>());
+
+ static ServiceState ExitStatusToState(int exitStatus);
+ static std::pair<String, String> ParseCheckOutput(const String& output);
+
+ static Array::Ptr SplitPerfdata(const String& perfdata);
+ static String FormatPerfdata(const Array::Ptr& perfdata, bool normalize = false);
+
+private:
+ PluginUtility();
+};
+
+}
+
+#endif /* PLUGINUTILITY_H */
diff --git a/lib/icinga/scheduleddowntime-apply.cpp b/lib/icinga/scheduleddowntime-apply.cpp
new file mode 100644
index 0000000..4f8aa47
--- /dev/null
+++ b/lib/icinga/scheduleddowntime-apply.cpp
@@ -0,0 +1,159 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/scheduleddowntime.hpp"
+#include "icinga/service.hpp"
+#include "config/configitembuilder.hpp"
+#include "config/applyrule.hpp"
+#include "base/initialize.hpp"
+#include "base/configtype.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+INITIALIZE_ONCE([]() {
+ ApplyRule::RegisterType("ScheduledDowntime", { "Host", "Service" });
+});
+
+bool ScheduledDowntime::EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter)
+{
+ if (!skipFilter && !rule.EvaluateFilter(frame))
+ return false;
+
+ auto& di (rule.GetDebugInfo());
+
+#ifdef _DEBUG
+ Log(LogDebug, "ScheduledDowntime")
+ << "Applying scheduled downtime '" << rule.GetName() << "' to object '" << checkable->GetName() << "' for rule " << di;
+#endif /* _DEBUG */
+
+ ConfigItemBuilder builder{di};
+ builder.SetType(ScheduledDowntime::TypeInstance);
+ builder.SetName(name);
+ builder.SetScope(frame.Locals->ShallowClone());
+ builder.SetIgnoreOnError(rule.GetIgnoreOnError());
+
+ builder.AddExpression(new ImportDefaultTemplatesExpression());
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "host_name"), OpSetLiteral, MakeLiteral(host->GetName()), di));
+
+ if (service)
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "service_name"), OpSetLiteral, MakeLiteral(service->GetShortName()), di));
+
+ String zone = checkable->GetZoneName();
+
+ if (!zone.IsEmpty())
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "zone"), OpSetLiteral, MakeLiteral(zone), di));
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "package"), OpSetLiteral, MakeLiteral(rule.GetPackage()), di));
+
+ builder.AddExpression(new OwnedExpression(rule.GetExpression()));
+
+ ConfigItem::Ptr downtimeItem = builder.Compile();
+ downtimeItem->Register();
+
+ return true;
+}
+
+bool ScheduledDowntime::EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter)
+{
+ auto& di (rule.GetDebugInfo());
+
+ CONTEXT("Evaluating 'apply' rule (" << di << ")");
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ ScriptFrame frame(true);
+ if (rule.GetScope())
+ rule.GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("host", host);
+ if (service)
+ frame.Locals->Set("service", service);
+
+ Value vinstances;
+
+ if (rule.GetFTerm()) {
+ try {
+ vinstances = rule.GetFTerm()->Evaluate(frame);
+ } catch (const std::exception&) {
+ /* Silently ignore errors here and assume there are no instances. */
+ return false;
+ }
+ } else {
+ vinstances = new Array({ "" });
+ }
+
+ bool match = false;
+
+ if (vinstances.IsObjectType<Array>()) {
+ if (!rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di));
+
+ Array::Ptr arr = vinstances;
+
+ ObjectLock olock(arr);
+ for (const Value& instance : arr) {
+ String name = rule.GetName();
+
+ if (!rule.GetFKVar().IsEmpty()) {
+ frame.Locals->Set(rule.GetFKVar(), instance);
+ name += instance;
+ }
+
+ if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter))
+ match = true;
+ }
+ } else if (vinstances.IsObjectType<Dictionary>()) {
+ if (rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di));
+
+ Dictionary::Ptr dict = vinstances;
+
+ for (const String& key : dict->GetKeys()) {
+ frame.Locals->Set(rule.GetFKVar(), key);
+ frame.Locals->Set(rule.GetFVVar(), dict->Get(key));
+
+ if (EvaluateApplyRuleInstance(checkable, rule.GetName() + key, frame, rule, skipFilter))
+ match = true;
+ }
+ }
+
+ return match;
+}
+
+void ScheduledDowntime::EvaluateApplyRules(const Host::Ptr& host)
+{
+ CONTEXT("Evaluating 'apply' rules for host '" << host->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(ScheduledDowntime::TypeInstance, Host::TypeInstance)) {
+ if (EvaluateApplyRule(host, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedHostRules(ScheduledDowntime::TypeInstance, host->GetName())) {
+ if (EvaluateApplyRule(host, *rule, true))
+ rule->AddMatch();
+ }
+}
+
+void ScheduledDowntime::EvaluateApplyRules(const Service::Ptr& service)
+{
+ CONTEXT("Evaluating 'apply' rules for service '" << service->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(ScheduledDowntime::TypeInstance, Service::TypeInstance)) {
+ if (EvaluateApplyRule(service, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedServiceRules(ScheduledDowntime::TypeInstance, service->GetHost()->GetName(), service->GetShortName())) {
+ if (EvaluateApplyRule(service, *rule, true))
+ rule->AddMatch();
+ }
+}
diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp
new file mode 100644
index 0000000..f23d3e4
--- /dev/null
+++ b/lib/icinga/scheduleddowntime.cpp
@@ -0,0 +1,393 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/scheduleddowntime.hpp"
+#include "icinga/scheduleddowntime-ti.cpp"
+#include "icinga/legacytimeperiod.hpp"
+#include "icinga/downtime.hpp"
+#include "icinga/service.hpp"
+#include "base/timer.hpp"
+#include "base/tlsutility.hpp"
+#include "base/configtype.hpp"
+#include "base/utility.hpp"
+#include "base/objectlock.hpp"
+#include "base/object-packer.hpp"
+#include "base/serializer.hpp"
+#include "base/convert.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include <boost/thread/once.hpp>
+#include <set>
+
+using namespace icinga;
+
+REGISTER_TYPE(ScheduledDowntime);
+
+static Timer::Ptr l_Timer;
+
+String ScheduledDowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ ScheduledDowntime::Ptr downtime = dynamic_pointer_cast<ScheduledDowntime>(context);
+
+ if (!downtime)
+ return "";
+
+ String name = downtime->GetHostName();
+
+ if (!downtime->GetServiceName().IsEmpty())
+ name += "!" + downtime->GetServiceName();
+
+ name += "!" + shortName;
+
+ return name;
+}
+
+Dictionary::Ptr ScheduledDowntimeNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid ScheduledDowntime name."));
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("host_name", tokens[0]);
+
+ if (tokens.size() > 2) {
+ result->Set("service_name", tokens[1]);
+ result->Set("name", tokens[2]);
+ } else {
+ result->Set("name", tokens[1]);
+ }
+
+ return result;
+}
+
+void ScheduledDowntime::OnAllConfigLoaded()
+{
+ ObjectImpl<ScheduledDowntime>::OnAllConfigLoaded();
+
+ if (!GetCheckable())
+ BOOST_THROW_EXCEPTION(ScriptError("ScheduledDowntime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
+
+ m_AllConfigLoaded.store(true);
+}
+
+void ScheduledDowntime::Start(bool runtimeCreated)
+{
+ ObjectImpl<ScheduledDowntime>::Start(runtimeCreated);
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, [this]() {
+ l_Timer = Timer::Create();
+ l_Timer->SetInterval(60);
+ l_Timer->OnTimerExpired.connect([](const Timer * const&) { TimerProc(); });
+ l_Timer->Start();
+ });
+
+ if (!IsPaused())
+ Utility::QueueAsyncCallback([this]() { CreateNextDowntime(); });
+}
+
+void ScheduledDowntime::TimerProc()
+{
+ for (const ScheduledDowntime::Ptr& sd : ConfigType::GetObjectsByType<ScheduledDowntime>()) {
+ if (sd->IsActive() && !sd->IsPaused()) {
+ try {
+ sd->CreateNextDowntime();
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ScheduledDowntime")
+ << "Exception occurred during creation of next downtime for scheduled downtime '"
+ << sd->GetName() << "': " << DiagnosticInformation(ex, false);
+ continue;
+ }
+
+ try {
+ sd->RemoveObsoleteDowntimes();
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ScheduledDowntime")
+ << "Exception occurred during removal of obsolete downtime for scheduled downtime '"
+ << sd->GetName() << "': " << DiagnosticInformation(ex, false);
+ }
+ }
+ }
+}
+
+Checkable::Ptr ScheduledDowntime::GetCheckable() const
+{
+ Host::Ptr host = Host::GetByName(GetHostName());
+
+ if (GetServiceName().IsEmpty())
+ return host;
+ else
+ return host->GetServiceByShortName(GetServiceName());
+}
+
+std::pair<double, double> ScheduledDowntime::FindRunningSegment(double minEnd)
+{
+ time_t refts = Utility::GetTime();
+ tm reference = Utility::LocalTime(refts);
+
+ Log(LogDebug, "ScheduledDowntime")
+ << "Finding running scheduled downtime segment for time " << refts
+ << " (minEnd " << (minEnd > 0 ? Utility::FormatDateTime("%c", minEnd) : "-") << ")";
+
+ Dictionary::Ptr ranges = GetRanges();
+
+ if (!ranges)
+ return std::make_pair(0, 0);
+
+ Array::Ptr segments = new Array();
+
+ Dictionary::Ptr bestSegment;
+ double bestBegin = 0.0, bestEnd = 0.0;
+ double now = Utility::GetTime();
+
+ ObjectLock olock(ranges);
+
+ /* Find the longest lasting (and longer than minEnd, if given) segment that's already running */
+ for (const Dictionary::Pair& kv : ranges) {
+ Log(LogDebug, "ScheduledDowntime")
+ << "Evaluating (running?) segment: " << kv.first << ": " << kv.second;
+
+ Dictionary::Ptr segment = LegacyTimePeriod::FindRunningSegment(kv.first, kv.second, &reference);
+
+ if (!segment)
+ continue;
+
+ double begin = segment->Get("begin");
+ double end = segment->Get("end");
+
+ Log(LogDebug, "ScheduledDowntime")
+ << "Considering (running?) segment: " << Utility::FormatDateTime("%c", begin) << " -> " << Utility::FormatDateTime("%c", end);
+
+ if (begin >= now || end < now) {
+ Log(LogDebug, "ScheduledDowntime") << "not running.";
+ continue;
+ }
+ if (minEnd && end <= minEnd) {
+ Log(LogDebug, "ScheduledDowntime") << "ending too early.";
+ continue;
+ }
+
+ if (!bestSegment || end > bestEnd) {
+ Log(LogDebug, "ScheduledDowntime") << "(best match yet)";
+ bestSegment = segment;
+ bestBegin = begin;
+ bestEnd = end;
+ }
+ }
+
+ if (bestSegment)
+ return std::make_pair(bestBegin, bestEnd);
+
+ return std::make_pair(0, 0);
+}
+
+std::pair<double, double> ScheduledDowntime::FindNextSegment()
+{
+ time_t refts = Utility::GetTime();
+ tm reference = Utility::LocalTime(refts);
+
+ Log(LogDebug, "ScheduledDowntime")
+ << "Finding next scheduled downtime segment for time " << refts;
+
+ Dictionary::Ptr ranges = GetRanges();
+
+ if (!ranges)
+ return std::make_pair(0, 0);
+
+ Array::Ptr segments = new Array();
+
+ Dictionary::Ptr bestSegment;
+ double bestBegin = 0.0, bestEnd = 0.0;
+ double now = Utility::GetTime();
+
+ ObjectLock olock(ranges);
+
+ /* Find the segment starting earliest */
+ for (const Dictionary::Pair& kv : ranges) {
+ Log(LogDebug, "ScheduledDowntime")
+ << "Evaluating segment: " << kv.first << ": " << kv.second;
+
+ Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference);
+
+ if (!segment)
+ continue;
+
+ double begin = segment->Get("begin");
+ double end = segment->Get("end");
+
+ Log(LogDebug, "ScheduledDowntime")
+ << "Considering segment: " << Utility::FormatDateTime("%c", begin) << " -> " << Utility::FormatDateTime("%c", end);
+
+ if (begin < now) {
+ Log(LogDebug, "ScheduledDowntime") << "already running.";
+ continue;
+ }
+
+ if (!bestSegment || begin < bestBegin) {
+ Log(LogDebug, "ScheduledDowntime") << "(best match yet)";
+ bestSegment = segment;
+ bestBegin = begin;
+ bestEnd = end;
+ }
+ }
+
+ if (bestSegment)
+ return std::make_pair(bestBegin, bestEnd);
+
+ return std::make_pair(0, 0);
+}
+
+void ScheduledDowntime::CreateNextDowntime()
+{
+ /* HA enabled zones. */
+ if (IsActive() && IsPaused()) {
+ Log(LogNotice, "Checkable")
+ << "Skipping downtime creation for HA-paused Scheduled Downtime object '" << GetName() << "'";
+ return;
+ }
+
+ double minEnd = 0;
+ auto downtimeOptionsHash (HashDowntimeOptions());
+
+ for (const Downtime::Ptr& downtime : GetCheckable()->GetDowntimes()) {
+ if (downtime->GetScheduledBy() != GetName())
+ continue;
+
+ auto configOwnerHash (downtime->GetConfigOwnerHash());
+ if (!configOwnerHash.IsEmpty() && configOwnerHash != downtimeOptionsHash)
+ continue;
+
+ double end = downtime->GetEndTime();
+ if (end > minEnd)
+ minEnd = end;
+
+ if (downtime->GetStartTime() < Utility::GetTime())
+ continue;
+
+ /* We've found a downtime that is owned by us and that hasn't started yet - we're done. */
+ return;
+ }
+
+ Log(LogDebug, "ScheduledDowntime")
+ << "Creating new Downtime for ScheduledDowntime \"" << GetName() << "\"";
+
+ std::pair<double, double> segment = FindRunningSegment(minEnd);
+ if (segment.first == 0 && segment.second == 0) {
+ segment = FindNextSegment();
+ if (segment.first == 0 && segment.second == 0)
+ return;
+ }
+
+ Downtime::Ptr downtime = Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(),
+ segment.first, segment.second,
+ GetFixed(), String(), GetDuration(), GetName(), GetName());
+ String downtimeName = downtime->GetName();
+
+ int childOptions = Downtime::ChildOptionsFromValue(GetChildOptions());
+ if (childOptions > 0) {
+ /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime.
+ * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children.
+ */
+ String triggerName;
+ if (childOptions == 1)
+ triggerName = downtimeName;
+
+ Log(LogNotice, "ScheduledDowntime")
+ << "Processing child options " << childOptions << " for downtime " << downtimeName;
+
+ for (const Checkable::Ptr& child : GetCheckable()->GetAllChildren()) {
+ Log(LogNotice, "ScheduledDowntime")
+ << "Scheduling downtime for child object " << child->GetName();
+
+ Downtime::Ptr childDowntime = Downtime::AddDowntime(child, GetAuthor(), GetComment(),
+ segment.first, segment.second, GetFixed(), triggerName, GetDuration(), GetName(), GetName());
+
+ Log(LogNotice, "ScheduledDowntime")
+ << "Add child downtime '" << childDowntime->GetName() << "'.";
+ }
+ }
+}
+
+void ScheduledDowntime::RemoveObsoleteDowntimes()
+{
+ auto name (GetName());
+ auto downtimeOptionsHash (HashDowntimeOptions());
+
+ // Just to be sure start and removal don't happen at the same time
+ auto threshold (Utility::GetTime() + 5 * 60);
+
+ for (const Downtime::Ptr& downtime : GetCheckable()->GetDowntimes()) {
+ if (downtime->GetScheduledBy() == name && downtime->GetStartTime() > threshold) {
+ auto configOwnerHash (downtime->GetConfigOwnerHash());
+
+ if (!configOwnerHash.IsEmpty() && configOwnerHash != downtimeOptionsHash)
+ Downtime::RemoveDowntime(downtime->GetName(), false, true);
+ }
+ }
+}
+
+void ScheduledDowntime::ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<ScheduledDowntime>::ValidateRanges(lvalue, utils);
+
+ if (!lvalue())
+ return;
+
+ /* create a fake time environment to validate the definitions */
+ time_t refts = Utility::GetTime();
+ tm reference = Utility::LocalTime(refts);
+ Array::Ptr segments = new Array();
+
+ ObjectLock olock(lvalue());
+ for (const Dictionary::Pair& kv : lvalue()) {
+ try {
+ tm begin_tm, end_tm;
+ int stride;
+ LegacyTimePeriod::ParseTimeRange(kv.first, &begin_tm, &end_tm, &stride, &reference);
+ } catch (const std::exception& ex) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time specification '" + kv.first + "': " + ex.what()));
+ }
+
+ try {
+ LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments);
+ } catch (const std::exception& ex) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time range definition '" + kv.second + "': " + ex.what()));
+ }
+ }
+}
+
+void ScheduledDowntime::ValidateChildOptions(const Lazy<Value>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<ScheduledDowntime>::ValidateChildOptions(lvalue, utils);
+
+ try {
+ Downtime::ChildOptionsFromValue(lvalue());
+ } catch (const std::exception&) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "child_options" }, "Invalid child_options specified"));
+ }
+}
+
+static const std::set<String> l_SDDowntimeOptions ({
+ "author", "child_options", "comment", "duration", "fixed", "ranges", "vars"
+});
+
+String ScheduledDowntime::HashDowntimeOptions()
+{
+ Dictionary::Ptr allOpts = Serialize(this, FAConfig);
+ Dictionary::Ptr opts = new Dictionary();
+
+ for (auto& opt : l_SDDowntimeOptions) {
+ opts->Set(opt, allOpts->Get(opt));
+ }
+
+ return SHA256(PackObject(opts));
+}
+
+bool ScheduledDowntime::AllConfigIsLoaded()
+{
+ return m_AllConfigLoaded.load();
+}
+
+std::atomic<bool> ScheduledDowntime::m_AllConfigLoaded (false);
diff --git a/lib/icinga/scheduleddowntime.hpp b/lib/icinga/scheduleddowntime.hpp
new file mode 100644
index 0000000..e701236
--- /dev/null
+++ b/lib/icinga/scheduleddowntime.hpp
@@ -0,0 +1,60 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef SCHEDULEDDOWNTIME_H
+#define SCHEDULEDDOWNTIME_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/scheduleddowntime-ti.hpp"
+#include "icinga/checkable.hpp"
+#include <atomic>
+
+namespace icinga
+{
+
+class ApplyRule;
+struct ScriptFrame;
+class Host;
+class Service;
+
+/**
+ * An Icinga scheduled downtime specification.
+ *
+ * @ingroup icinga
+ */
+class ScheduledDowntime final : public ObjectImpl<ScheduledDowntime>
+{
+public:
+ DECLARE_OBJECT(ScheduledDowntime);
+ DECLARE_OBJECTNAME(ScheduledDowntime);
+
+ Checkable::Ptr GetCheckable() const;
+
+ static void EvaluateApplyRules(const intrusive_ptr<Host>& host);
+ static void EvaluateApplyRules(const intrusive_ptr<Service>& service);
+ static bool AllConfigIsLoaded();
+
+ void ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
+ void ValidateChildOptions(const Lazy<Value>& lvalue, const ValidationUtils& utils) override;
+ String HashDowntimeOptions();
+
+protected:
+ void OnAllConfigLoaded() override;
+ void Start(bool runtimeCreated) override;
+
+private:
+ static void TimerProc();
+
+ std::pair<double, double> FindRunningSegment(double minEnd = 0);
+ std::pair<double, double> FindNextSegment();
+ void CreateNextDowntime();
+ void RemoveObsoleteDowntimes();
+
+ static std::atomic<bool> m_AllConfigLoaded;
+
+ static bool EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter);
+ static bool EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter = false);
+};
+
+}
+
+#endif /* SCHEDULEDDOWNTIME_H */
diff --git a/lib/icinga/scheduleddowntime.ti b/lib/icinga/scheduleddowntime.ti
new file mode 100644
index 0000000..1653f27
--- /dev/null
+++ b/lib/icinga/scheduleddowntime.ti
@@ -0,0 +1,76 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#impl_include "icinga/service.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+class ScheduledDowntimeNameComposer : public NameComposer
+{
+public:
+ virtual String MakeName(const String& shortName, const Object::Ptr& context) const;
+ virtual Dictionary::Ptr ParseName(const String& name) const;
+};
+}}}
+
+class ScheduledDowntime : CustomVarObject < ScheduledDowntimeNameComposer
+{
+ // Scheduled Downtimes have a dependency on Downtimes. This is to make sure ScheduledDowntimes are activated after
+ // the Downtimes (and other checkables)
+ activation_priority 20;
+
+ load_after Host;
+ load_after Service;
+
+ [config, protected, no_user_modify, required, navigation(host)] name(Host) host_name {
+ navigate {{{
+ return Host::GetByName(GetHostName());
+ }}}
+ };
+ [config, protected, no_user_modify, navigation(service)] String service_name {
+ track {{{
+ if (!oldValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), oldValue);
+ DependencyGraph::RemoveDependency(this, service.get());
+ }
+
+ if (!newValue.IsEmpty()) {
+ Service::Ptr service = Service::GetByNamePair(GetHostName(), newValue);
+ DependencyGraph::AddDependency(this, service.get());
+ }
+ }}}
+ navigate {{{
+ if (GetServiceName().IsEmpty())
+ return nullptr;
+
+ Host::Ptr host = Host::GetByName(GetHostName());
+ return host->GetServiceByShortName(GetServiceName());
+ }}}
+ };
+
+ [config, required] String author;
+ [config, required] String comment;
+
+ [config] double duration;
+ [config] bool fixed {
+ default {{{ return true; }}}
+ };
+
+ [config] Value child_options {
+ default {{{ return "DowntimeNoChildren"; }}}
+ };
+
+ [config, required] Dictionary::Ptr ranges;
+};
+
+validator ScheduledDowntime {
+ Dictionary ranges {
+ String "*";
+ };
+};
+
+}
diff --git a/lib/icinga/service-apply.cpp b/lib/icinga/service-apply.cpp
new file mode 100644
index 0000000..4419e0b
--- /dev/null
+++ b/lib/icinga/service-apply.cpp
@@ -0,0 +1,133 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/service.hpp"
+#include "config/configitembuilder.hpp"
+#include "config/applyrule.hpp"
+#include "base/initialize.hpp"
+#include "base/configtype.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/workqueue.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+INITIALIZE_ONCE([]() {
+ ApplyRule::RegisterType("Service", { "Host" });
+});
+
+bool Service::EvaluateApplyRuleInstance(const Host::Ptr& host, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter)
+{
+ if (!skipFilter && !rule.EvaluateFilter(frame))
+ return false;
+
+ auto& di (rule.GetDebugInfo());
+
+#ifdef _DEBUG
+ Log(LogDebug, "Service")
+ << "Applying service '" << name << "' to host '" << host->GetName() << "' for rule " << di;
+#endif /* _DEBUG */
+
+ ConfigItemBuilder builder{di};
+ builder.SetType(Service::TypeInstance);
+ builder.SetName(name);
+ builder.SetScope(frame.Locals->ShallowClone());
+ builder.SetIgnoreOnError(rule.GetIgnoreOnError());
+
+ builder.AddExpression(new ImportDefaultTemplatesExpression());
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "host_name"), OpSetLiteral, MakeLiteral(host->GetName()), di));
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "name"), OpSetLiteral, MakeLiteral(name), di));
+
+ String zone = host->GetZoneName();
+
+ if (!zone.IsEmpty())
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "zone"), OpSetLiteral, MakeLiteral(zone), di));
+
+ builder.AddExpression(new SetExpression(MakeIndexer(ScopeThis, "package"), OpSetLiteral, MakeLiteral(rule.GetPackage()), di));
+
+ builder.AddExpression(new OwnedExpression(rule.GetExpression()));
+
+ ConfigItem::Ptr serviceItem = builder.Compile();
+ serviceItem->Register();
+
+ return true;
+}
+
+bool Service::EvaluateApplyRule(const Host::Ptr& host, const ApplyRule& rule, bool skipFilter)
+{
+ auto& di (rule.GetDebugInfo());
+
+ CONTEXT("Evaluating 'apply' rule (" << di << ")");
+
+ ScriptFrame frame(true);
+ if (rule.GetScope())
+ rule.GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("host", host);
+
+ Value vinstances;
+
+ if (rule.GetFTerm()) {
+ try {
+ vinstances = rule.GetFTerm()->Evaluate(frame);
+ } catch (const std::exception&) {
+ /* Silently ignore errors here and assume there are no instances. */
+ return false;
+ }
+ } else {
+ vinstances = new Array({ "" });
+ }
+
+ bool match = false;
+
+ if (vinstances.IsObjectType<Array>()) {
+ if (!rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di));
+
+ Array::Ptr arr = vinstances;
+
+ ObjectLock olock(arr);
+ for (const Value& instance : arr) {
+ String name = rule.GetName();
+
+ if (!rule.GetFKVar().IsEmpty()) {
+ frame.Locals->Set(rule.GetFKVar(), instance);
+ name += instance;
+ }
+
+ if (EvaluateApplyRuleInstance(host, name, frame, rule, skipFilter))
+ match = true;
+ }
+ } else if (vinstances.IsObjectType<Dictionary>()) {
+ if (rule.GetFVVar().IsEmpty())
+ BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di));
+
+ Dictionary::Ptr dict = vinstances;
+
+ for (const String& key : dict->GetKeys()) {
+ frame.Locals->Set(rule.GetFKVar(), key);
+ frame.Locals->Set(rule.GetFVVar(), dict->Get(key));
+
+ if (EvaluateApplyRuleInstance(host, rule.GetName() + key, frame, rule, skipFilter))
+ match = true;
+ }
+ }
+
+ return match;
+}
+
+void Service::EvaluateApplyRules(const Host::Ptr& host)
+{
+ CONTEXT("Evaluating 'apply' rules for host '" << host->GetName() << "'");
+
+ for (auto& rule : ApplyRule::GetRules(Service::TypeInstance, Host::TypeInstance)) {
+ if (EvaluateApplyRule(host, *rule))
+ rule->AddMatch();
+ }
+
+ for (auto& rule : ApplyRule::GetTargetedHostRules(Service::TypeInstance, host->GetName())) {
+ if (EvaluateApplyRule(host, *rule, true))
+ rule->AddMatch();
+ }
+}
diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp
new file mode 100644
index 0000000..d831136
--- /dev/null
+++ b/lib/icinga/service.cpp
@@ -0,0 +1,287 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/service.hpp"
+#include "icinga/service-ti.cpp"
+#include "icinga/servicegroup.hpp"
+#include "icinga/scheduleddowntime.hpp"
+#include "icinga/pluginutility.hpp"
+#include "base/objectlock.hpp"
+#include "base/convert.hpp"
+#include "base/utility.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(Service);
+
+boost::signals2::signal<void (const Service::Ptr&, const CheckResult::Ptr&, const MessageOrigin::Ptr&)> Service::OnHostProblemChanged;
+
+String ServiceNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ Service::Ptr service = dynamic_pointer_cast<Service>(context);
+
+ if (!service)
+ return "";
+
+ return service->GetHostName() + "!" + shortName;
+}
+
+Dictionary::Ptr ServiceNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Service name."));
+
+ return new Dictionary({
+ { "host_name", tokens[0] },
+ { "name", tokens[1] }
+ });
+}
+
+void Service::OnAllConfigLoaded()
+{
+ ObjectImpl<Service>::OnAllConfigLoaded();
+
+ String zoneName = GetZoneName();
+
+ if (!zoneName.IsEmpty()) {
+ Zone::Ptr zone = Zone::GetByName(zoneName);
+
+ if (zone && zone->IsGlobal())
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Service '" + GetName() + "' cannot be put into global zone '" + zone->GetName() + "'."));
+ }
+
+ m_Host = Host::GetByName(GetHostName());
+
+ if (m_Host)
+ m_Host->AddService(this);
+
+ ServiceGroup::EvaluateObjectRules(this);
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups) {
+ groups = groups->ShallowClone();
+
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ ServiceGroup::Ptr sg = ServiceGroup::GetByName(name);
+
+ if (sg)
+ sg->ResolveGroupMembership(this, true);
+ }
+ }
+}
+
+void Service::CreateChildObjects(const Type::Ptr& childType)
+{
+ if (childType == ScheduledDowntime::TypeInstance)
+ ScheduledDowntime::EvaluateApplyRules(this);
+
+ if (childType == Notification::TypeInstance)
+ Notification::EvaluateApplyRules(this);
+
+ if (childType == Dependency::TypeInstance)
+ Dependency::EvaluateApplyRules(this);
+}
+
+Service::Ptr Service::GetByNamePair(const String& hostName, const String& serviceName)
+{
+ if (!hostName.IsEmpty()) {
+ Host::Ptr host = Host::GetByName(hostName);
+
+ if (!host)
+ return nullptr;
+
+ return host->GetServiceByShortName(serviceName);
+ } else {
+ return Service::GetByName(serviceName);
+ }
+}
+
+Host::Ptr Service::GetHost() const
+{
+ return m_Host;
+}
+
+/* keep in sync with Host::GetSeverity()
+ * One could think it may be smart to use an enum and some bitmask math here.
+ * But the only thing the consuming icingaweb2 cares about is being able to
+ * sort by severity. It is therefore easier to keep them seperated here. */
+int Service::GetSeverity() const
+{
+ int severity;
+
+ ObjectLock olock(this);
+ ServiceState state = GetStateRaw();
+
+ if (!HasBeenChecked()) {
+ severity = 16;
+ } else if (state == ServiceOK) {
+ severity = 0;
+ } else {
+ switch (state) {
+ case ServiceWarning:
+ severity = 32;
+ break;
+ case ServiceUnknown:
+ severity = 64;
+ break;
+ case ServiceCritical:
+ severity = 128;
+ break;
+ default:
+ severity = 256;
+ }
+
+ Host::Ptr host = GetHost();
+ ObjectLock hlock (host);
+ if (host->GetState() != HostUp) {
+ severity += 1024;
+ } else {
+ if (IsAcknowledged())
+ severity += 512;
+ else if (IsInDowntime())
+ severity += 256;
+ else
+ severity += 2048;
+ }
+ hlock.Unlock();
+ }
+
+ olock.Unlock();
+
+ return severity;
+}
+
+bool Service::GetHandled() const
+{
+ return Checkable::GetHandled() || (m_Host && m_Host->GetProblem());
+}
+
+bool Service::IsStateOK(ServiceState state) const
+{
+ return state == ServiceOK;
+}
+
+void Service::SaveLastState(ServiceState state, double timestamp)
+{
+ if (state == ServiceOK)
+ SetLastStateOK(timestamp);
+ else if (state == ServiceWarning)
+ SetLastStateWarning(timestamp);
+ else if (state == ServiceCritical)
+ SetLastStateCritical(timestamp);
+ else if (state == ServiceUnknown)
+ SetLastStateUnknown(timestamp);
+}
+
+ServiceState Service::StateFromString(const String& state)
+{
+ if (state == "OK")
+ return ServiceOK;
+ else if (state == "WARNING")
+ return ServiceWarning;
+ else if (state == "CRITICAL")
+ return ServiceCritical;
+ else
+ return ServiceUnknown;
+}
+
+String Service::StateToString(ServiceState state)
+{
+ switch (state) {
+ case ServiceOK:
+ return "OK";
+ case ServiceWarning:
+ return "WARNING";
+ case ServiceCritical:
+ return "CRITICAL";
+ case ServiceUnknown:
+ default:
+ return "UNKNOWN";
+ }
+}
+
+StateType Service::StateTypeFromString(const String& type)
+{
+ if (type == "SOFT")
+ return StateTypeSoft;
+ else
+ return StateTypeHard;
+}
+
+String Service::StateTypeToString(StateType type)
+{
+ if (type == StateTypeSoft)
+ return "SOFT";
+ else
+ return "HARD";
+}
+
+bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const
+{
+ if (macro == "state") {
+ *result = StateToString(GetState());
+ return true;
+ } else if (macro == "state_id") {
+ *result = GetState();
+ return true;
+ } else if (macro == "state_type") {
+ *result = StateTypeToString(GetStateType());
+ return true;
+ } else if (macro == "last_state") {
+ *result = StateToString(GetLastState());
+ return true;
+ } else if (macro == "last_state_id") {
+ *result = GetLastState();
+ return true;
+ } else if (macro == "last_state_type") {
+ *result = StateTypeToString(GetLastStateType());
+ return true;
+ } else if (macro == "last_state_change") {
+ *result = static_cast<long>(GetLastStateChange());
+ return true;
+ } else if (macro == "downtime_depth") {
+ *result = GetDowntimeDepth();
+ return true;
+ } else if (macro == "duration_sec") {
+ *result = Utility::GetTime() - GetLastStateChange();
+ return true;
+ }
+
+ if (cr) {
+ if (macro == "latency") {
+ *result = cr->CalculateLatency();
+ return true;
+ } else if (macro == "execution_time") {
+ *result = cr->CalculateExecutionTime();
+ return true;
+ } else if (macro == "output") {
+ *result = cr->GetOutput();
+ return true;
+ } else if (macro == "perfdata") {
+ *result = PluginUtility::FormatPerfdata(cr->GetPerformanceData());
+ return true;
+ } else if (macro == "check_source") {
+ *result = cr->GetCheckSource();
+ return true;
+ } else if (macro == "scheduling_source") {
+ *result = cr->GetSchedulingSource();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::pair<Host::Ptr, Service::Ptr> icinga::GetHostService(const Checkable::Ptr& checkable)
+{
+ Service::Ptr service = dynamic_pointer_cast<Service>(checkable);
+
+ if (service)
+ return std::make_pair(service->GetHost(), service);
+ else
+ return std::make_pair(static_pointer_cast<Host>(checkable), nullptr);
+}
diff --git a/lib/icinga/service.hpp b/lib/icinga/service.hpp
new file mode 100644
index 0000000..ac27c3d
--- /dev/null
+++ b/lib/icinga/service.hpp
@@ -0,0 +1,65 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/service-ti.hpp"
+#include "icinga/macroresolver.hpp"
+#include "icinga/host.hpp"
+#include <tuple>
+
+using std::tie;
+
+namespace icinga
+{
+
+/**
+ * An Icinga service.
+ *
+ * @ingroup icinga
+ */
+class Service final : public ObjectImpl<Service>, public MacroResolver
+{
+public:
+ DECLARE_OBJECT(Service);
+ DECLARE_OBJECTNAME(Service);
+
+ static Service::Ptr GetByNamePair(const String& hostName, const String& serviceName);
+
+ Host::Ptr GetHost() const override;
+ int GetSeverity() const override;
+ bool GetHandled() const override;
+
+ bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const override;
+
+ bool IsStateOK(ServiceState state) const override;
+ void SaveLastState(ServiceState state, double timestamp) override;
+
+ static ServiceState StateFromString(const String& state);
+ static String StateToString(ServiceState state);
+
+ static StateType StateTypeFromString(const String& state);
+ static String StateTypeToString(StateType state);
+
+ static void EvaluateApplyRules(const Host::Ptr& host);
+
+ void OnAllConfigLoaded() override;
+
+ static boost::signals2::signal<void (const Service::Ptr&, const CheckResult::Ptr&, const MessageOrigin::Ptr&)> OnHostProblemChanged;
+
+protected:
+ void CreateChildObjects(const Type::Ptr& childType) override;
+
+private:
+ Host::Ptr m_Host;
+
+ static bool EvaluateApplyRuleInstance(const Host::Ptr& host, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter);
+ static bool EvaluateApplyRule(const Host::Ptr& host, const ApplyRule& rule, bool skipFilter = false);
+};
+
+std::pair<Host::Ptr, Service::Ptr> GetHostService(const Checkable::Ptr& checkable);
+
+}
+
+#endif /* SERVICE_H */
diff --git a/lib/icinga/service.ti b/lib/icinga/service.ti
new file mode 100644
index 0000000..12c2d8c
--- /dev/null
+++ b/lib/icinga/service.ti
@@ -0,0 +1,71 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/checkable.hpp"
+#include "icinga/host.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/customvarobject.hpp"
+#impl_include "icinga/servicegroup.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+code {{{
+class ServiceNameComposer : public NameComposer
+{
+public:
+ virtual String MakeName(const String& shortName, const Object::Ptr& context) const;
+ virtual Dictionary::Ptr ParseName(const String& name) const;
+};
+}}}
+
+class Service : Checkable < ServiceNameComposer
+{
+ load_after ApiListener;
+ load_after Endpoint;
+ load_after Host;
+ load_after Zone;
+
+ [config, no_user_modify, required, signal_with_old_value] array(name(ServiceGroup)) groups {
+ default {{{ return new Array(); }}}
+ };
+
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetShortName();
+ else
+ return displayName;
+ }}}
+ };
+ [config, no_user_modify, required] name(Host) host_name;
+ [no_storage, navigation] Host::Ptr host {
+ get;
+ navigate {{{
+ return GetHost();
+ }}}
+ };
+ [enum, no_storage] ServiceState "state" {
+ get {{{
+ return GetStateRaw();
+ }}}
+ };
+ [enum, no_storage] ServiceState last_state {
+ get {{{
+ return GetLastStateRaw();
+ }}}
+ };
+ [enum, no_storage] ServiceState last_hard_state {
+ get {{{
+ return GetLastHardStateRaw();
+ }}}
+ };
+ [state] Timestamp last_state_ok (LastStateOK);
+ [state] Timestamp last_state_warning;
+ [state] Timestamp last_state_critical;
+ [state] Timestamp last_state_unknown;
+};
+
+}
diff --git a/lib/icinga/servicegroup.cpp b/lib/icinga/servicegroup.cpp
new file mode 100644
index 0000000..d21f852
--- /dev/null
+++ b/lib/icinga/servicegroup.cpp
@@ -0,0 +1,111 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/servicegroup.hpp"
+#include "icinga/servicegroup-ti.cpp"
+#include "config/objectrule.hpp"
+#include "config/configitem.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/workqueue.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(ServiceGroup);
+
+INITIALIZE_ONCE([]() {
+ ObjectRule::RegisterType("ServiceGroup");
+});
+
+bool ServiceGroup::EvaluateObjectRule(const Service::Ptr& service, const ConfigItem::Ptr& group)
+{
+ String groupName = group->GetName();
+
+ CONTEXT("Evaluating rule for group '" << groupName << "'");
+
+ Host::Ptr host = service->GetHost();
+
+ ScriptFrame frame(true);
+ if (group->GetScope())
+ group->GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("host", host);
+ frame.Locals->Set("service", service);
+
+ if (!group->GetFilter()->Evaluate(frame).GetValue().ToBool())
+ return false;
+
+ Log(LogDebug, "ServiceGroup")
+ << "Assigning membership for group '" << groupName << "' to service '" << service->GetName() << "'";
+
+ Array::Ptr groups = service->GetGroups();
+
+ if (groups && !groups->Contains(groupName))
+ groups->Add(groupName);
+
+ return true;
+}
+
+void ServiceGroup::EvaluateObjectRules(const Service::Ptr& service)
+{
+ CONTEXT("Evaluating group membership for service '" << service->GetName() << "'");
+
+ for (const ConfigItem::Ptr& group : ConfigItem::GetItems(ServiceGroup::TypeInstance))
+ {
+ if (!group->GetFilter())
+ continue;
+
+ EvaluateObjectRule(service, group);
+ }
+}
+
+std::set<Service::Ptr> ServiceGroup::GetMembers() const
+{
+ std::unique_lock<std::mutex> lock(m_ServiceGroupMutex);
+ return m_Members;
+}
+
+void ServiceGroup::AddMember(const Service::Ptr& service)
+{
+ service->AddGroup(GetName());
+
+ std::unique_lock<std::mutex> lock(m_ServiceGroupMutex);
+ m_Members.insert(service);
+}
+
+void ServiceGroup::RemoveMember(const Service::Ptr& service)
+{
+ std::unique_lock<std::mutex> lock(m_ServiceGroupMutex);
+ m_Members.erase(service);
+}
+
+bool ServiceGroup::ResolveGroupMembership(const Service::Ptr& service, bool add, int rstack) {
+
+ if (add && rstack > 20) {
+ Log(LogWarning, "ServiceGroup")
+ << "Too many nested groups for group '" << GetName() << "': Service '"
+ << service->GetName() << "' membership assignment failed.";
+
+ return false;
+ }
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups && groups->GetLength() > 0) {
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ ServiceGroup::Ptr group = ServiceGroup::GetByName(name);
+
+ if (group && !group->ResolveGroupMembership(service, add, rstack + 1))
+ return false;
+ }
+ }
+
+ if (add)
+ AddMember(service);
+ else
+ RemoveMember(service);
+
+ return true;
+}
diff --git a/lib/icinga/servicegroup.hpp b/lib/icinga/servicegroup.hpp
new file mode 100644
index 0000000..f2d0ab7
--- /dev/null
+++ b/lib/icinga/servicegroup.hpp
@@ -0,0 +1,43 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef SERVICEGROUP_H
+#define SERVICEGROUP_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/servicegroup-ti.hpp"
+#include "icinga/service.hpp"
+
+namespace icinga
+{
+
+class ConfigItem;
+
+/**
+ * An Icinga service group.
+ *
+ * @ingroup icinga
+ */
+class ServiceGroup final : public ObjectImpl<ServiceGroup>
+{
+public:
+ DECLARE_OBJECT(ServiceGroup);
+ DECLARE_OBJECTNAME(ServiceGroup);
+
+ std::set<Service::Ptr> GetMembers() const;
+ void AddMember(const Service::Ptr& service);
+ void RemoveMember(const Service::Ptr& service);
+
+ bool ResolveGroupMembership(const Service::Ptr& service, bool add = true, int rstack = 0);
+
+ static void EvaluateObjectRules(const Service::Ptr& service);
+
+private:
+ mutable std::mutex m_ServiceGroupMutex;
+ std::set<Service::Ptr> m_Members;
+
+ static bool EvaluateObjectRule(const Service::Ptr& service, const intrusive_ptr<ConfigItem>& group);
+};
+
+}
+
+#endif /* SERVICEGROUP_H */
diff --git a/lib/icinga/servicegroup.ti b/lib/icinga/servicegroup.ti
new file mode 100644
index 0000000..7daf9d4
--- /dev/null
+++ b/lib/icinga/servicegroup.ti
@@ -0,0 +1,28 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class ServiceGroup : CustomVarObject
+{
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetName();
+ else
+ return displayName;
+ }}}
+ };
+
+ [config, no_user_modify] array(name(ServiceGroup)) groups;
+ [config] String notes;
+ [config] String notes_url;
+ [config] String action_url;
+};
+
+}
diff --git a/lib/icinga/timeperiod.cpp b/lib/icinga/timeperiod.cpp
new file mode 100644
index 0000000..db3272e
--- /dev/null
+++ b/lib/icinga/timeperiod.cpp
@@ -0,0 +1,399 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/timeperiod.hpp"
+#include "icinga/timeperiod-ti.cpp"
+#include "icinga/legacytimeperiod.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/exception.hpp"
+#include "base/logger.hpp"
+#include "base/timer.hpp"
+#include "base/utility.hpp"
+#include <boost/thread/once.hpp>
+
+using namespace icinga;
+
+REGISTER_TYPE(TimePeriod);
+
+static Timer::Ptr l_UpdateTimer;
+
+void TimePeriod::Start(bool runtimeCreated)
+{
+ ObjectImpl<TimePeriod>::Start(runtimeCreated);
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, [this]() {
+ l_UpdateTimer = Timer::Create();
+ l_UpdateTimer->SetInterval(300);
+ l_UpdateTimer->OnTimerExpired.connect([](const Timer * const&) { UpdateTimerHandler(); });
+ l_UpdateTimer->Start();
+ });
+
+ /* Pre-fill the time period for the next 24 hours. */
+ double now = Utility::GetTime();
+ UpdateRegion(now, now + 24 * 3600, true);
+#ifdef _DEBUG
+ Dump();
+#endif /* _DEBUG */
+}
+
+void TimePeriod::AddSegment(double begin, double end)
+{
+ ASSERT(OwnsLock());
+
+ Log(LogDebug, "TimePeriod")
+ << "Adding segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
+ << Utility::FormatDateTime("%c", end) << "' to TimePeriod '" << GetName() << "'";
+
+ if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
+ SetValidBegin(begin);
+
+ if (GetValidEnd().IsEmpty() || end > GetValidEnd())
+ SetValidEnd(end);
+
+ Array::Ptr segments = GetSegments();
+
+ if (segments) {
+ /* Try to merge the new segment into an existing segment. */
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ if (segment->Get("begin") <= begin && segment->Get("end") >= end)
+ return; /* New segment is fully contained in this segment. */
+
+ if (segment->Get("begin") >= begin && segment->Get("end") <= end) {
+ segment->Set("begin", begin);
+ segment->Set("end", end); /* Extend an existing segment to both sides */
+ return;
+ }
+
+ if (segment->Get("end") >= begin && segment->Get("end") <= end) {
+ segment->Set("end", end); /* Extend an existing segment to right. */
+ return;
+ }
+
+ if (segment->Get("begin") >= begin && segment->Get("begin") <= end) {
+ segment->Set("begin", begin); /* Extend an existing segment to left. */
+ return;
+ }
+
+ }
+ }
+
+ /* Create new segment if we weren't able to merge this into an existing segment. */
+ Dictionary::Ptr segment = new Dictionary({
+ { "begin", begin },
+ { "end", end }
+ });
+
+ if (!segments) {
+ segments = new Array();
+ SetSegments(segments);
+ }
+
+ segments->Add(segment);
+}
+
+void TimePeriod::AddSegment(const Dictionary::Ptr& segment)
+{
+ AddSegment(segment->Get("begin"), segment->Get("end"));
+}
+
+void TimePeriod::RemoveSegment(double begin, double end)
+{
+ ASSERT(OwnsLock());
+
+ Log(LogDebug, "TimePeriod")
+ << "Removing segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
+ << Utility::FormatDateTime("%c", end) << "' from TimePeriod '" << GetName() << "'";
+
+ if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
+ SetValidBegin(begin);
+
+ if (GetValidEnd().IsEmpty() || end > GetValidEnd())
+ SetValidEnd(end);
+
+ Array::Ptr segments = GetSegments();
+
+ if (!segments)
+ return;
+
+ Array::Ptr newSegments = new Array();
+
+ /* Try to split or adjust an existing segment. */
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ /* Fully contained in the specified range? */
+ if (segment->Get("begin") >= begin && segment->Get("end") <= end)
+ // Don't add the old segment, because the segment is fully contained into our range
+ continue;
+
+ /* Not overlapping at all? */
+ if (segment->Get("end") < begin || segment->Get("begin") > end) {
+ newSegments->Add(segment);
+ continue;
+ }
+
+ /* Cut between */
+ if (segment->Get("begin") < begin && segment->Get("end") > end) {
+ newSegments->Add(new Dictionary({
+ { "begin", segment->Get("begin") },
+ { "end", begin }
+ }));
+
+ newSegments->Add(new Dictionary({
+ { "begin", end },
+ { "end", segment->Get("end") }
+ }));
+ // Don't add the old segment, because we have now two new segments and a gap between
+ continue;
+ }
+
+ /* Adjust the begin/end timestamps so as to not overlap with the specified range. */
+ if (segment->Get("begin") > begin && segment->Get("begin") < end)
+ segment->Set("begin", end);
+
+ if (segment->Get("end") > begin && segment->Get("end") < end)
+ segment->Set("end", begin);
+
+ newSegments->Add(segment);
+ }
+
+ SetSegments(newSegments);
+
+#ifdef _DEBUG
+ Dump();
+#endif /* _DEBUG */
+}
+
+void TimePeriod::RemoveSegment(const Dictionary::Ptr& segment)
+{
+ RemoveSegment(segment->Get("begin"), segment->Get("end"));
+}
+
+void TimePeriod::PurgeSegments(double end)
+{
+ ASSERT(OwnsLock());
+
+ Log(LogDebug, "TimePeriod")
+ << "Purging segments older than '" << Utility::FormatDateTime("%c", end)
+ << "' from TimePeriod '" << GetName() << "'";
+
+ if (GetValidBegin().IsEmpty() || end < GetValidBegin())
+ return;
+
+ SetValidBegin(end);
+
+ Array::Ptr segments = GetSegments();
+
+ if (!segments)
+ return;
+
+ Array::Ptr newSegments = new Array();
+
+ /* Remove old segments. */
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ if (segment->Get("end") >= end)
+ newSegments->Add(segment);
+ }
+
+ SetSegments(newSegments);
+}
+
+void TimePeriod::Merge(const TimePeriod::Ptr& timeperiod, bool include)
+{
+ Log(LogDebug, "TimePeriod")
+ << "Merge TimePeriod '" << GetName() << "' with '" << timeperiod->GetName() << "' "
+ << "Method: " << (include ? "include" : "exclude");
+
+ Array::Ptr segments = timeperiod->GetSegments();
+
+ if (segments) {
+ ObjectLock dlock(segments);
+ ObjectLock ilock(this);
+ for (const Dictionary::Ptr& segment : segments) {
+ include ? AddSegment(segment) : RemoveSegment(segment);
+ }
+ }
+}
+
+void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting)
+{
+ if (clearExisting) {
+ ObjectLock olock(this);
+ SetSegments(new Array());
+ } else {
+ if (begin < GetValidEnd())
+ begin = GetValidEnd();
+
+ if (end < GetValidEnd())
+ return;
+ }
+
+ Array::Ptr segments = GetUpdate()->Invoke({ this, begin, end });
+
+ {
+ ObjectLock olock(this);
+ RemoveSegment(begin, end);
+
+ if (segments) {
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ AddSegment(segment);
+ }
+ }
+ }
+
+ bool preferInclude = GetPreferIncludes();
+
+ /* First handle the non preferred timeranges */
+ Array::Ptr timeranges = preferInclude ? GetExcludes() : GetIncludes();
+
+ if (timeranges) {
+ ObjectLock olock(timeranges);
+ for (const String& name : timeranges) {
+ const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
+
+ if (timeperiod)
+ Merge(timeperiod, !preferInclude);
+ }
+ }
+
+ /* Preferred timeranges must be handled at the end */
+ timeranges = preferInclude ? GetIncludes() : GetExcludes();
+
+ if (timeranges) {
+ ObjectLock olock(timeranges);
+ for (const String& name : timeranges) {
+ const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
+
+ if (timeperiod)
+ Merge(timeperiod, preferInclude);
+ }
+ }
+}
+
+bool TimePeriod::GetIsInside() const
+{
+ return IsInside(Utility::GetTime());
+}
+
+bool TimePeriod::IsInside(double ts) const
+{
+ ObjectLock olock(this);
+
+ if (GetValidBegin().IsEmpty() || ts < GetValidBegin() || GetValidEnd().IsEmpty() || ts > GetValidEnd())
+ return true; /* Assume that all invalid regions are "inside". */
+
+ Array::Ptr segments = GetSegments();
+
+ if (segments) {
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ if (ts > segment->Get("begin") && ts < segment->Get("end"))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+double TimePeriod::FindNextTransition(double begin)
+{
+ ObjectLock olock(this);
+
+ Array::Ptr segments = GetSegments();
+
+ double closestTransition = -1;
+
+ if (segments) {
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ if (segment->Get("begin") > begin && (segment->Get("begin") < closestTransition || closestTransition == -1))
+ closestTransition = segment->Get("begin");
+
+ if (segment->Get("end") > begin && (segment->Get("end") < closestTransition || closestTransition == -1))
+ closestTransition = segment->Get("end");
+ }
+ }
+
+ return closestTransition;
+}
+
+void TimePeriod::UpdateTimerHandler()
+{
+ double now = Utility::GetTime();
+
+ for (const TimePeriod::Ptr& tp : ConfigType::GetObjectsByType<TimePeriod>()) {
+ if (!tp->IsActive())
+ continue;
+
+ double valid_end;
+
+ {
+ ObjectLock olock(tp);
+ tp->PurgeSegments(now - 3600);
+
+ valid_end = tp->GetValidEnd();
+ }
+
+ tp->UpdateRegion(valid_end, now + 24 * 3600, false);
+#ifdef _DEBUG
+ tp->Dump();
+#endif /* _DEBUG */
+ }
+}
+
+void TimePeriod::Dump()
+{
+ ObjectLock olock(this);
+
+ Array::Ptr segments = GetSegments();
+
+ Log(LogDebug, "TimePeriod")
+ << "Dumping TimePeriod '" << GetName() << "'";
+
+ Log(LogDebug, "TimePeriod")
+ << "Valid from '" << Utility::FormatDateTime("%c", GetValidBegin())
+ << "' until '" << Utility::FormatDateTime("%c", GetValidEnd());
+
+ if (segments) {
+ ObjectLock dlock(segments);
+ for (const Dictionary::Ptr& segment : segments) {
+ Log(LogDebug, "TimePeriod")
+ << "Segment: " << Utility::FormatDateTime("%c", segment->Get("begin")) << " <-> "
+ << Utility::FormatDateTime("%c", segment->Get("end"));
+ }
+ }
+
+ Log(LogDebug, "TimePeriod", "---");
+}
+
+void TimePeriod::ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ if (!lvalue())
+ return;
+
+ /* create a fake time environment to validate the definitions */
+ time_t refts = Utility::GetTime();
+ tm reference = Utility::LocalTime(refts);
+ Array::Ptr segments = new Array();
+
+ ObjectLock olock(lvalue());
+ for (const Dictionary::Pair& kv : lvalue()) {
+ try {
+ tm begin_tm, end_tm;
+ int stride;
+ LegacyTimePeriod::ParseTimeRange(kv.first, &begin_tm, &end_tm, &stride, &reference);
+ } catch (const std::exception& ex) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time specification '" + kv.first + "': " + ex.what()));
+ }
+
+ try {
+ LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments);
+ } catch (const std::exception& ex) {
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time range definition '" + kv.second + "': " + ex.what()));
+ }
+ }
+}
diff --git a/lib/icinga/timeperiod.hpp b/lib/icinga/timeperiod.hpp
new file mode 100644
index 0000000..a5a2f73
--- /dev/null
+++ b/lib/icinga/timeperiod.hpp
@@ -0,0 +1,50 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef TIMEPERIOD_H
+#define TIMEPERIOD_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/timeperiod-ti.hpp"
+
+namespace icinga
+{
+
+/**
+ * A time period.
+ *
+ * @ingroup icinga
+ */
+class TimePeriod final : public ObjectImpl<TimePeriod>
+{
+public:
+ DECLARE_OBJECT(TimePeriod);
+ DECLARE_OBJECTNAME(TimePeriod);
+
+ void Start(bool runtimeCreated) override;
+
+ void UpdateRegion(double begin, double end, bool clearExisting);
+
+ bool GetIsInside() const override;
+
+ bool IsInside(double ts) const;
+ double FindNextTransition(double begin);
+
+ void ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
+
+private:
+ void AddSegment(double s, double end);
+ void AddSegment(const Dictionary::Ptr& segment);
+ void RemoveSegment(double begin, double end);
+ void RemoveSegment(const Dictionary::Ptr& segment);
+ void PurgeSegments(double end);
+
+ void Merge(const TimePeriod::Ptr& timeperiod, bool include = true);
+
+ void Dump();
+
+ static void UpdateTimerHandler();
+};
+
+}
+
+#endif /* TIMEPERIOD_H */
diff --git a/lib/icinga/timeperiod.ti b/lib/icinga/timeperiod.ti
new file mode 100644
index 0000000..bba272e
--- /dev/null
+++ b/lib/icinga/timeperiod.ti
@@ -0,0 +1,47 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#include "base/function.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class TimePeriod : CustomVarObject
+{
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetName();
+ else
+ return displayName;
+ }}}
+ };
+ [config, signal_with_old_value] Dictionary::Ptr ranges;
+ [config, required] Function::Ptr update;
+ [config] bool prefer_includes {
+ default {{{ return true; }}}
+ };
+ [config, required, signal_with_old_value] array(name(TimePeriod)) excludes {
+ default {{{ return new Array(); }}}
+ };
+ [config, required, signal_with_old_value] array(name(TimePeriod)) includes {
+ default {{{ return new Array(); }}}
+ };
+ [state, no_user_modify] Value valid_begin;
+ [state, no_user_modify] Value valid_end;
+ [state, no_user_modify] Array::Ptr segments;
+ [no_storage] bool is_inside {
+ get;
+ };
+};
+
+validator TimePeriod {
+ Dictionary ranges {
+ String "*";
+ };
+};
+
+}
diff --git a/lib/icinga/user.cpp b/lib/icinga/user.cpp
new file mode 100644
index 0000000..4d99db7
--- /dev/null
+++ b/lib/icinga/user.cpp
@@ -0,0 +1,103 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/user.hpp"
+#include "icinga/user-ti.cpp"
+#include "icinga/usergroup.hpp"
+#include "icinga/notification.hpp"
+#include "icinga/usergroup.hpp"
+#include "base/objectlock.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(User);
+
+void User::OnConfigLoaded()
+{
+ ObjectImpl<User>::OnConfigLoaded();
+
+ SetTypeFilter(FilterArrayToInt(GetTypes(), Notification::GetTypeFilterMap(), ~0));
+ SetStateFilter(FilterArrayToInt(GetStates(), Notification::GetStateFilterMap(), ~0));
+}
+
+void User::OnAllConfigLoaded()
+{
+ ObjectImpl<User>::OnAllConfigLoaded();
+
+ UserGroup::EvaluateObjectRules(this);
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups) {
+ groups = groups->ShallowClone();
+
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ UserGroup::Ptr ug = UserGroup::GetByName(name);
+
+ if (ug)
+ ug->ResolveGroupMembership(this, true);
+ }
+ }
+}
+
+void User::Stop(bool runtimeRemoved)
+{
+ ObjectImpl<User>::Stop(runtimeRemoved);
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups) {
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ UserGroup::Ptr ug = UserGroup::GetByName(name);
+
+ if (ug)
+ ug->ResolveGroupMembership(this, false);
+ }
+ }
+}
+
+void User::AddGroup(const String& name)
+{
+ std::unique_lock<std::mutex> lock(m_UserMutex);
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups && groups->Contains(name))
+ return;
+
+ if (!groups)
+ groups = new Array();
+
+ groups->Add(name);
+}
+
+TimePeriod::Ptr User::GetPeriod() const
+{
+ return TimePeriod::GetByName(GetPeriodRaw());
+}
+
+void User::ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<User>::ValidateStates(lvalue, utils);
+
+ int filter = FilterArrayToInt(lvalue(), Notification::GetStateFilterMap(), 0);
+
+ if (filter == -1 || (filter & ~(StateFilterUp | StateFilterDown | StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid."));
+}
+
+void User::ValidateTypes(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<User>::ValidateTypes(lvalue, utils);
+
+ int filter = FilterArrayToInt(lvalue(), Notification::GetTypeFilterMap(), 0);
+
+ if (filter == -1 || (filter & ~(NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved |
+ NotificationCustom | NotificationAcknowledgement | NotificationProblem | NotificationRecovery |
+ NotificationFlappingStart | NotificationFlappingEnd)) != 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "types" }, "Type filter is invalid."));
+}
diff --git a/lib/icinga/user.hpp b/lib/icinga/user.hpp
new file mode 100644
index 0000000..14e59c2
--- /dev/null
+++ b/lib/icinga/user.hpp
@@ -0,0 +1,44 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef USER_H
+#define USER_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/user-ti.hpp"
+#include "icinga/timeperiod.hpp"
+#include "remote/messageorigin.hpp"
+
+namespace icinga
+{
+
+/**
+ * A User.
+ *
+ * @ingroup icinga
+ */
+class User final : public ObjectImpl<User>
+{
+public:
+ DECLARE_OBJECT(User);
+ DECLARE_OBJECTNAME(User);
+
+ void AddGroup(const String& name);
+
+ /* Notifications */
+ TimePeriod::Ptr GetPeriod() const;
+
+ void ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
+ void ValidateTypes(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
+
+protected:
+ void Stop(bool runtimeRemoved) override;
+
+ void OnConfigLoaded() override;
+ void OnAllConfigLoaded() override;
+private:
+ mutable std::mutex m_UserMutex;
+};
+
+}
+
+#endif /* USER_H */
diff --git a/lib/icinga/user.ti b/lib/icinga/user.ti
new file mode 100644
index 0000000..8b8c43a
--- /dev/null
+++ b/lib/icinga/user.ti
@@ -0,0 +1,47 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+#include "base/array.hpp"
+#impl_include "icinga/usergroup.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class User : CustomVarObject
+{
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetName();
+ else
+ return displayName;
+ }}}
+ };
+ [config, no_user_modify, required, signal_with_old_value] array(name(UserGroup)) groups {
+ default {{{ return new Array(); }}}
+ };
+ [config, navigation] name(TimePeriod) period (PeriodRaw) {
+ navigate {{{
+ return TimePeriod::GetByName(GetPeriodRaw());
+ }}}
+ };
+
+ [config] array(Value) types;
+ [no_user_view, no_user_modify] int type_filter_real (TypeFilter);
+ [config] array(Value) states;
+ [no_user_view, no_user_modify] int state_filter_real (StateFilter);
+
+ [config] String email;
+ [config] String pager;
+
+ [config] bool enable_notifications {
+ default {{{ return true; }}}
+ };
+
+ [state] Timestamp last_notification;
+};
+
+}
diff --git a/lib/icinga/usergroup.cpp b/lib/icinga/usergroup.cpp
new file mode 100644
index 0000000..27ae45b
--- /dev/null
+++ b/lib/icinga/usergroup.cpp
@@ -0,0 +1,128 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/usergroup.hpp"
+#include "icinga/usergroup-ti.cpp"
+#include "icinga/notification.hpp"
+#include "config/objectrule.hpp"
+#include "config/configitem.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/logger.hpp"
+#include "base/context.hpp"
+#include "base/workqueue.hpp"
+
+using namespace icinga;
+
+REGISTER_TYPE(UserGroup);
+
+INITIALIZE_ONCE([]() {
+ ObjectRule::RegisterType("UserGroup");
+});
+
+bool UserGroup::EvaluateObjectRule(const User::Ptr& user, const ConfigItem::Ptr& group)
+{
+ String groupName = group->GetName();
+
+ CONTEXT("Evaluating rule for group '" << groupName << "'");
+
+ ScriptFrame frame(true);
+ if (group->GetScope())
+ group->GetScope()->CopyTo(frame.Locals);
+ frame.Locals->Set("user", user);
+
+ if (!group->GetFilter()->Evaluate(frame).GetValue().ToBool())
+ return false;
+
+ Log(LogDebug, "UserGroup")
+ << "Assigning membership for group '" << groupName << "' to user '" << user->GetName() << "'";
+
+ Array::Ptr groups = user->GetGroups();
+
+ if (groups && !groups->Contains(groupName))
+ groups->Add(groupName);
+
+ return true;
+}
+
+void UserGroup::EvaluateObjectRules(const User::Ptr& user)
+{
+ CONTEXT("Evaluating group membership for user '" << user->GetName() << "'");
+
+ for (const ConfigItem::Ptr& group : ConfigItem::GetItems(UserGroup::TypeInstance))
+ {
+ if (!group->GetFilter())
+ continue;
+
+ EvaluateObjectRule(user, group);
+ }
+}
+
+std::set<User::Ptr> UserGroup::GetMembers() const
+{
+ std::unique_lock<std::mutex> lock(m_UserGroupMutex);
+ return m_Members;
+}
+
+void UserGroup::AddMember(const User::Ptr& user)
+{
+ user->AddGroup(GetName());
+
+ std::unique_lock<std::mutex> lock(m_UserGroupMutex);
+ m_Members.insert(user);
+}
+
+void UserGroup::RemoveMember(const User::Ptr& user)
+{
+ std::unique_lock<std::mutex> lock(m_UserGroupMutex);
+ m_Members.erase(user);
+}
+
+std::set<Notification::Ptr> UserGroup::GetNotifications() const
+{
+ std::unique_lock<std::mutex> lock(m_UserGroupMutex);
+ return m_Notifications;
+}
+
+void UserGroup::AddNotification(const Notification::Ptr& notification)
+{
+ std::unique_lock<std::mutex> lock(m_UserGroupMutex);
+ m_Notifications.insert(notification);
+}
+
+void UserGroup::RemoveNotification(const Notification::Ptr& notification)
+{
+ std::unique_lock<std::mutex> lock(m_UserGroupMutex);
+ m_Notifications.erase(notification);
+}
+
+bool UserGroup::ResolveGroupMembership(const User::Ptr& user, bool add, int rstack) {
+
+ if (add && rstack > 20) {
+ Log(LogWarning, "UserGroup")
+ << "Too many nested groups for group '" << GetName() << "': User '"
+ << user->GetName() << "' membership assignment failed.";
+
+ return false;
+ }
+
+ Array::Ptr groups = GetGroups();
+
+ if (groups && groups->GetLength() > 0) {
+ ObjectLock olock(groups);
+
+ for (const String& name : groups) {
+ UserGroup::Ptr group = UserGroup::GetByName(name);
+
+ if (group && !group->ResolveGroupMembership(user, add, rstack + 1))
+ return false;
+ }
+ }
+
+ if (add)
+ AddMember(user);
+ else
+ RemoveMember(user);
+
+ return true;
+}
+
diff --git a/lib/icinga/usergroup.hpp b/lib/icinga/usergroup.hpp
new file mode 100644
index 0000000..c6f82a1
--- /dev/null
+++ b/lib/icinga/usergroup.hpp
@@ -0,0 +1,49 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef USERGROUP_H
+#define USERGROUP_H
+
+#include "icinga/i2-icinga.hpp"
+#include "icinga/usergroup-ti.hpp"
+#include "icinga/user.hpp"
+
+namespace icinga
+{
+
+class ConfigItem;
+class Notification;
+
+/**
+ * An Icinga user group.
+ *
+ * @ingroup icinga
+ */
+class UserGroup final : public ObjectImpl<UserGroup>
+{
+public:
+ DECLARE_OBJECT(UserGroup);
+ DECLARE_OBJECTNAME(UserGroup);
+
+ std::set<User::Ptr> GetMembers() const;
+ void AddMember(const User::Ptr& user);
+ void RemoveMember(const User::Ptr& user);
+
+ std::set<intrusive_ptr<Notification>> GetNotifications() const;
+ void AddNotification(const intrusive_ptr<Notification>& notification);
+ void RemoveNotification(const intrusive_ptr<Notification>& notification);
+
+ bool ResolveGroupMembership(const User::Ptr& user, bool add = true, int rstack = 0);
+
+ static void EvaluateObjectRules(const User::Ptr& user);
+
+private:
+ mutable std::mutex m_UserGroupMutex;
+ std::set<User::Ptr> m_Members;
+ std::set<intrusive_ptr<Notification>> m_Notifications;
+
+ static bool EvaluateObjectRule(const User::Ptr& user, const intrusive_ptr<ConfigItem>& group);
+};
+
+}
+
+#endif /* USERGROUP_H */
diff --git a/lib/icinga/usergroup.ti b/lib/icinga/usergroup.ti
new file mode 100644
index 0000000..e955c5e
--- /dev/null
+++ b/lib/icinga/usergroup.ti
@@ -0,0 +1,25 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/customvarobject.hpp"
+
+library icinga;
+
+namespace icinga
+{
+
+class UserGroup : CustomVarObject
+{
+ [config] String display_name {
+ get {{{
+ String displayName = m_DisplayName.load();
+ if (displayName.IsEmpty())
+ return GetName();
+ else
+ return displayName;
+ }}}
+ };
+
+ [config, no_user_modify] array(name(UserGroup)) groups;
+};
+
+}