1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "icinga/apiactions.hpp"
4 #include "icinga/service.hpp"
5 #include "icinga/servicegroup.hpp"
6 #include "icinga/hostgroup.hpp"
7 #include "icinga/pluginutility.hpp"
8 #include "icinga/checkcommand.hpp"
9 #include "icinga/eventcommand.hpp"
10 #include "icinga/notificationcommand.hpp"
11 #include "remote/apiaction.hpp"
12 #include "remote/apilistener.hpp"
13 #include "remote/pkiutility.hpp"
14 #include "remote/httputility.hpp"
15 #include "base/utility.hpp"
16 #include "base/convert.hpp"
19 using namespace icinga;
21 REGISTER_APIACTION(process_check_result, "Service;Host", &ApiActions::ProcessCheckResult);
22 REGISTER_APIACTION(reschedule_check, "Service;Host", &ApiActions::RescheduleCheck);
23 REGISTER_APIACTION(send_custom_notification, "Service;Host", &ApiActions::SendCustomNotification);
24 REGISTER_APIACTION(delay_notification, "Service;Host", &ApiActions::DelayNotification);
25 REGISTER_APIACTION(acknowledge_problem, "Service;Host", &ApiActions::AcknowledgeProblem);
26 REGISTER_APIACTION(remove_acknowledgement, "Service;Host", &ApiActions::RemoveAcknowledgement);
27 REGISTER_APIACTION(add_comment, "Service;Host", &ApiActions::AddComment);
28 REGISTER_APIACTION(remove_comment, "Service;Host;Comment", &ApiActions::RemoveComment);
29 REGISTER_APIACTION(schedule_downtime, "Service;Host", &ApiActions::ScheduleDowntime);
30 REGISTER_APIACTION(remove_downtime, "Service;Host;Downtime", &ApiActions::RemoveDowntime);
31 REGISTER_APIACTION(shutdown_process, "", &ApiActions::ShutdownProcess);
32 REGISTER_APIACTION(restart_process, "", &ApiActions::RestartProcess);
33 REGISTER_APIACTION(generate_ticket, "", &ApiActions::GenerateTicket);
35 Dictionary::Ptr ApiActions::CreateResult(int code, const String& status,
36 const Dictionary::Ptr& additional)
38 Dictionary::Ptr result = new Dictionary({
44 additional->CopyTo(result);
49 Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object,
50 const Dictionary::Ptr& params)
52 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
55 return ApiActions::CreateResult(404,
56 "Cannot process passive check result for non-existent object.");
58 if (!checkable->GetEnablePassiveChecks())
59 return ApiActions::CreateResult(403, "Passive checks are disabled for object '" + checkable->GetName() + "'.");
63 tie(host, service) = GetHostService(checkable);
65 if (!params->Contains("exit_status"))
66 return ApiActions::CreateResult(400, "Parameter 'exit_status' is required.");
68 int exitStatus = HttpUtility::GetLastParameter(params, "exit_status");
75 else if (exitStatus == 1)
76 state = ServiceCritical;
78 return ApiActions::CreateResult(400, "Invalid 'exit_status' for Host "
79 + checkable->GetName() + ".");
81 state = PluginUtility::ExitStatusToState(exitStatus);
84 if (!params->Contains("plugin_output"))
85 return ApiActions::CreateResult(400, "Parameter 'plugin_output' is required");
87 CheckResult::Ptr cr = new CheckResult();
88 cr->SetOutput(HttpUtility::GetLastParameter(params, "plugin_output"));
91 if (params->Contains("execution_start"))
92 cr->SetExecutionStart(HttpUtility::GetLastParameter(params, "execution_start"));
94 if (params->Contains("execution_end"))
95 cr->SetExecutionEnd(HttpUtility::GetLastParameter(params, "execution_end"));
97 cr->SetCheckSource(HttpUtility::GetLastParameter(params, "check_source"));
99 Value perfData = params->Get("performance_data");
101 /* Allow to pass a performance data string from Icinga Web 2 next to the new Array notation. */
102 if (perfData.IsString())
103 cr->SetPerformanceData(PluginUtility::SplitPerfdata(perfData));
105 cr->SetPerformanceData(perfData);
107 cr->SetCommand(params->Get("check_command"));
109 /* Mark this check result as passive. */
110 cr->SetActive(false);
112 /* Result TTL allows to overrule the next expected freshness check. */
113 if (params->Contains("ttl"))
114 cr->SetTtl(HttpUtility::GetLastParameter(params, "ttl"));
116 checkable->ProcessCheckResult(cr);
118 return ApiActions::CreateResult(200, "Successfully processed check result for object '" + checkable->GetName() + "'.");
121 Dictionary::Ptr ApiActions::RescheduleCheck(const ConfigObject::Ptr& object,
122 const Dictionary::Ptr& params)
124 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
127 return ApiActions::CreateResult(404, "Cannot reschedule check for non-existent object.");
129 if (Convert::ToBool(HttpUtility::GetLastParameter(params, "force")))
130 checkable->SetForceNextCheck(true);
133 if (params->Contains("next_check"))
134 nextCheck = HttpUtility::GetLastParameter(params, "next_check");
136 nextCheck = Utility::GetTime();
138 checkable->SetNextCheck(nextCheck);
140 /* trigger update event for DB IDO */
141 Checkable::OnNextCheckUpdated(checkable);
143 return ApiActions::CreateResult(200, "Successfully rescheduled check for object '" + checkable->GetName() + "'.");
146 Dictionary::Ptr ApiActions::SendCustomNotification(const ConfigObject::Ptr& object,
147 const Dictionary::Ptr& params)
149 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
152 return ApiActions::CreateResult(404, "Cannot send notification for non-existent object.");
154 if (!params->Contains("author"))
155 return ApiActions::CreateResult(400, "Parameter 'author' is required.");
157 if (!params->Contains("comment"))
158 return ApiActions::CreateResult(400, "Parameter 'comment' is required.");
160 if (Convert::ToBool(HttpUtility::GetLastParameter(params, "force")))
161 checkable->SetForceNextNotification(true);
163 Checkable::OnNotificationsRequested(checkable, NotificationCustom, checkable->GetLastCheckResult(),
164 HttpUtility::GetLastParameter(params, "author"), HttpUtility::GetLastParameter(params, "comment"), nullptr);
166 return ApiActions::CreateResult(200, "Successfully sent custom notification for object '" + checkable->GetName() + "'.");
169 Dictionary::Ptr ApiActions::DelayNotification(const ConfigObject::Ptr& object,
170 const Dictionary::Ptr& params)
172 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
175 return ApiActions::CreateResult(404, "Cannot delay notifications for non-existent object");
177 if (!params->Contains("timestamp"))
178 return ApiActions::CreateResult(400, "A timestamp is required to delay notifications");
180 for (const Notification::Ptr& notification : checkable->GetNotifications()) {
181 notification->SetNextNotification(HttpUtility::GetLastParameter(params, "timestamp"));
184 return ApiActions::CreateResult(200, "Successfully delayed notifications for object '" + checkable->GetName() + "'.");
187 Dictionary::Ptr ApiActions::AcknowledgeProblem(const ConfigObject::Ptr& object,
188 const Dictionary::Ptr& params)
190 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
193 return ApiActions::CreateResult(404, "Cannot acknowledge problem for non-existent object.");
195 if (!params->Contains("author") || !params->Contains("comment"))
196 return ApiActions::CreateResult(400, "Acknowledgements require author and comment.");
198 AcknowledgementType sticky = AcknowledgementNormal;
200 bool persistent = false;
201 double timestamp = 0.0;
203 if (params->Contains("sticky") && HttpUtility::GetLastParameter(params, "sticky"))
204 sticky = AcknowledgementSticky;
205 if (params->Contains("notify"))
206 notify = HttpUtility::GetLastParameter(params, "notify");
207 if (params->Contains("persistent"))
208 persistent = HttpUtility::GetLastParameter(params, "persistent");
209 if (params->Contains("expiry")) {
210 timestamp = HttpUtility::GetLastParameter(params, "expiry");
212 if (timestamp <= Utility::GetTime())
213 return ApiActions::CreateResult(409, "Acknowledgement 'expiry' timestamp must be in the future for object " + checkable->GetName());
218 Service::Ptr service;
219 tie(host, service) = GetHostService(checkable);
222 if (host->GetState() == HostUp)
223 return ApiActions::CreateResult(409, "Host " + checkable->GetName() + " is UP.");
225 if (service->GetState() == ServiceOK)
226 return ApiActions::CreateResult(409, "Service " + checkable->GetName() + " is OK.");
229 Comment::AddComment(checkable, CommentAcknowledgement, HttpUtility::GetLastParameter(params, "author"),
230 HttpUtility::GetLastParameter(params, "comment"), persistent, timestamp);
231 checkable->AcknowledgeProblem(HttpUtility::GetLastParameter(params, "author"),
232 HttpUtility::GetLastParameter(params, "comment"), sticky, notify, persistent, timestamp);
234 return ApiActions::CreateResult(200, "Successfully acknowledged problem for object '" + checkable->GetName() + "'.");
237 Dictionary::Ptr ApiActions::RemoveAcknowledgement(const ConfigObject::Ptr& object,
238 const Dictionary::Ptr& params)
240 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
243 return ApiActions::CreateResult(404,
244 "Cannot remove acknowledgement for non-existent checkable object "
245 + object->GetName() + ".");
247 checkable->ClearAcknowledgement();
248 checkable->RemoveCommentsByType(CommentAcknowledgement);
250 return ApiActions::CreateResult(200, "Successfully removed acknowledgement for object '" + checkable->GetName() + "'.");
253 Dictionary::Ptr ApiActions::AddComment(const ConfigObject::Ptr& object,
254 const Dictionary::Ptr& params)
256 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
259 return ApiActions::CreateResult(404, "Cannot add comment for non-existent object");
261 if (!params->Contains("author") || !params->Contains("comment"))
262 return ApiActions::CreateResult(400, "Comments require author and comment.");
264 String commentName = Comment::AddComment(checkable, CommentUser,
265 HttpUtility::GetLastParameter(params, "author"),
266 HttpUtility::GetLastParameter(params, "comment"), false, 0);
268 Comment::Ptr comment = Comment::GetByName(commentName);
270 Dictionary::Ptr additional = new Dictionary({
271 { "name", commentName },
272 { "legacy_id", comment->GetLegacyId() }
275 return ApiActions::CreateResult(200, "Successfully added comment '"
276 + commentName + "' for object '" + checkable->GetName()
280 Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
281 const Dictionary::Ptr& params)
283 Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
286 std::set<Comment::Ptr> comments = checkable->GetComments();
288 for (const Comment::Ptr& comment : comments) {
289 Comment::RemoveComment(comment->GetName());
292 return ApiActions::CreateResult(200, "Successfully removed all comments for object '" + checkable->GetName() + "'.");
295 Comment::Ptr comment = static_pointer_cast<Comment>(object);
298 return ApiActions::CreateResult(404, "Cannot remove non-existent comment object.");
300 String commentName = comment->GetName();
302 Comment::RemoveComment(commentName);
304 return ApiActions::CreateResult(200, "Successfully removed comment '" + commentName + "'.");
307 Dictionary::Ptr ApiActions::ScheduleDowntime(const ConfigObject::Ptr& object,
308 const Dictionary::Ptr& params)
310 Checkable::Ptr checkable = static_pointer_cast<Checkable>(object);
313 return ApiActions::CreateResult(404, "Can't schedule downtime for non-existent object.");
315 if (!params->Contains("start_time") || !params->Contains("end_time") ||
316 !params->Contains("author") || !params->Contains("comment")) {
318 return ApiActions::CreateResult(400, "Options 'start_time', 'end_time', 'author' and 'comment' are required");
322 if (params->Contains("fixed"))
323 fixed = HttpUtility::GetLastParameter(params, "fixed");
325 if (!fixed && !params->Contains("duration"))
326 return ApiActions::CreateResult(400, "Option 'duration' is required for flexible downtime");
328 double duration = 0.0;
329 if (params->Contains("duration"))
330 duration = HttpUtility::GetLastParameter(params, "duration");
333 if (params->Contains("trigger_name"))
334 triggerName = HttpUtility::GetLastParameter(params, "trigger_name");
336 String author = HttpUtility::GetLastParameter(params, "author");
337 String comment = HttpUtility::GetLastParameter(params, "comment");
338 double startTime = HttpUtility::GetLastParameter(params, "start_time");
339 double endTime = HttpUtility::GetLastParameter(params, "end_time");
341 DowntimeChildOptions childOptions = DowntimeNoChildren;
342 if (params->Contains("child_options")) {
344 childOptions = Downtime::ChildOptionsFromValue(HttpUtility::GetLastParameter(params, "child_options"));
345 } catch (const std::exception&) {
346 return ApiActions::CreateResult(400, "Option 'child_options' provided an invalid value.");
350 String downtimeName = Downtime::AddDowntime(checkable, author, comment, startTime, endTime,
351 fixed, triggerName, duration);
353 Downtime::Ptr downtime = Downtime::GetByName(downtimeName);
355 Dictionary::Ptr additional = new Dictionary({
356 { "name", downtimeName },
357 { "legacy_id", downtime->GetLegacyId() }
360 /* Schedule downtime for all child objects. */
361 if (childOptions != DowntimeNoChildren) {
362 /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime.
363 * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children.
365 if (childOptions == DowntimeTriggeredChildren)
366 triggerName = downtimeName;
368 Log(LogNotice, "ApiActions")
369 << "Processing child options " << childOptions << " for downtime " << downtimeName;
371 ArrayData childDowntimes;
373 for (const Checkable::Ptr& child : checkable->GetAllChildren()) {
374 Log(LogNotice, "ApiActions")
375 << "Scheduling downtime for child object " << child->GetName();
377 String childDowntimeName = Downtime::AddDowntime(child, author, comment, startTime, endTime,
378 fixed, triggerName, duration);
380 Log(LogNotice, "ApiActions")
381 << "Add child downtime '" << childDowntimeName << "'.";
383 Downtime::Ptr childDowntime = Downtime::GetByName(childDowntimeName);
385 childDowntimes.push_back(new Dictionary({
386 { "name", childDowntimeName },
387 { "legacy_id", childDowntime->GetLegacyId() }
391 additional->Set("child_downtimes", new Array(std::move(childDowntimes)));
394 return ApiActions::CreateResult(200, "Successfully scheduled downtime '" +
395 downtimeName + "' for object '" + checkable->GetName() + "'.", additional);
398 Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
399 const Dictionary::Ptr& params)
401 Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
404 std::set<Downtime::Ptr> downtimes = checkable->GetDowntimes();
406 for (const Downtime::Ptr& downtime : downtimes) {
407 Downtime::RemoveDowntime(downtime->GetName(), true);
410 return ApiActions::CreateResult(200, "Successfully removed all downtimes for object '" + checkable->GetName() + "'.");
413 Downtime::Ptr downtime = static_pointer_cast<Downtime>(object);
416 return ApiActions::CreateResult(404, "Cannot remove non-existent downtime object.");
418 String downtimeName = downtime->GetName();
420 Downtime::RemoveDowntime(downtimeName, true);
422 return ApiActions::CreateResult(200, "Successfully removed downtime '" + downtimeName + "'.");
425 Dictionary::Ptr ApiActions::ShutdownProcess(const ConfigObject::Ptr& object,
426 const Dictionary::Ptr& params)
428 Application::RequestShutdown();
430 return ApiActions::CreateResult(200, "Shutting down Icinga 2.");
433 Dictionary::Ptr ApiActions::RestartProcess(const ConfigObject::Ptr& object,
434 const Dictionary::Ptr& params)
436 Application::RequestRestart();
438 return ApiActions::CreateResult(200, "Restarting Icinga 2.");
441 Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&,
442 const Dictionary::Ptr& params)
444 if (!params->Contains("cn"))
445 return ApiActions::CreateResult(400, "Option 'cn' is required");
447 String cn = HttpUtility::GetLastParameter(params, "cn");
449 ApiListener::Ptr listener = ApiListener::GetInstance();
450 String salt = listener->GetTicketSalt();
453 return ApiActions::CreateResult(500, "Ticket salt is not configured in ApiListener object");
455 String ticket = PBKDF2_SHA1(cn, salt, 50000);
457 Dictionary::Ptr additional = new Dictionary({
461 return ApiActions::CreateResult(200, "Generated PKI ticket '" + ticket + "' for common name '"
462 + cn + "'.", additional);