diff options
Diffstat (limited to 'lib/icinga')
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(©); +} + +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; +}; + +} |