mirror of
https://github.com/Icinga/icinga2.git
synced 2025-08-21 01:28:21 +02:00
Services downtimes scheduled via the `all_services` flag get already removed automatically when removing their parent downtimes (introduced with #8913). Now, this commit makes it possible to perform the same actions for all child downtimes, i.e. not only for those of service objects, but for all child objects represented in the dependency tree.
971 lines
34 KiB
C++
971 lines
34 KiB
C++
/* 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");
|
|
|
|
Downtime::Ptr trigger;
|
|
String triggerName = HttpUtility::GetLastParameter(params, "trigger_name");
|
|
|
|
if (!triggerName.IsEmpty()) {
|
|
trigger = Downtime::GetByName(triggerName);
|
|
|
|
if (!trigger) {
|
|
return ApiActions::CreateResult(404, "Won't schedule downtime with non-existent trigger downtime.");
|
|
}
|
|
}
|
|
|
|
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, trigger, 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, trigger, 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) {
|
|
trigger = downtime;
|
|
}
|
|
|
|
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, trigger, duration, String(), String(), downtimeName);
|
|
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, trigger, 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, DowntimeRemovedByUser, 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, DowntimeRemovedByUser, 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);
|
|
}
|