@@ -33,55 +33,6 @@ using namespace icinga;
3333
3434REGISTER_FUNCTION_NONCONST (Internal, IfwApiCheck, &IfwApiCheckTask::ScriptFunc, " checkable:cr:resolvedMacros:useResolvedMacros" );
3535
36- static void ReportIfwCheckResult (
37- const Checkable::Ptr& checkable, const Value& cmdLine, const CheckResult::Ptr& cr,
38- const String& output, double start, double end, int exitcode = 3 , const Array::Ptr& perfdata = nullptr
39- )
40- {
41- if (Checkable::ExecuteCommandProcessFinishedHandler) {
42- ProcessResult pr;
43- pr.PID = -1 ;
44- pr.Output = perfdata ? output + " |" + String (perfdata->Join (" " )) : output;
45- pr.ExecutionStart = start;
46- pr.ExecutionEnd = end;
47- pr.ExitStatus = exitcode;
48-
49- Checkable::ExecuteCommandProcessFinishedHandler (cmdLine, pr);
50- } else {
51- auto splittedPerfdata (perfdata);
52-
53- if (perfdata) {
54- splittedPerfdata = new Array ();
55- ObjectLock oLock (perfdata);
56-
57- for (String pv : perfdata) {
58- PluginUtility::SplitPerfdata (pv)->CopyTo (splittedPerfdata);
59- }
60- }
61-
62- cr->SetOutput (output);
63- cr->SetPerformanceData (splittedPerfdata);
64- cr->SetState ((ServiceState)exitcode);
65- cr->SetExitStatus (exitcode);
66- cr->SetExecutionStart (start);
67- cr->SetExecutionEnd (end);
68- cr->SetCommand (cmdLine);
69-
70- checkable->ProcessCheckResult (cr);
71- }
72- }
73-
74- static void ReportIfwCheckResult (
75- boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Value& cmdLine,
76- const CheckResult::Ptr& cr, const String& output, double start
77- )
78- {
79- double end = Utility::GetTime ();
80- CpuBoundWork cbw (yc);
81-
82- ReportIfwCheckResult (checkable, cmdLine, cr, output, start, end);
83- }
84-
8536static const char * GetUnderstandableError (const std::exception& ex)
8637{
8738 auto se (dynamic_cast <const boost::system::system_error*>(&ex));
@@ -93,10 +44,12 @@ static const char* GetUnderstandableError(const std::exception& ex)
9344 return ex.what ();
9445}
9546
47+ // Note: If DoIfwNetIo returns due to an error, the plugin output of the specified CheckResult (cr) will always be set,
48+ // and if it was successful, the cr exit status, plugin state and performance data (if any) will also be overridden.
49+ // Therefore, you have to take care yourself of setting all the other necessary fields for the check result.
9650static void DoIfwNetIo (
97- boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Array::Ptr& cmdLine,
98- const CheckResult::Ptr& cr, const String& psCommand, const String& psHost, const String& san, const String& psPort,
99- AsioTlsStream& conn, boost::beast::http::request<boost::beast::http::string_body>& req, double start
51+ boost::asio::yield_context yc, const CheckResult::Ptr& cr, const String& psCommand, const String& psHost, const String& san,
52+ const String& psPort, AsioTlsStream& conn, boost::beast::http::request<boost::beast::http::string_body>& req
10053)
10154{
10255 namespace http = boost::beast::http;
@@ -107,11 +60,7 @@ static void DoIfwNetIo(
10760 try {
10861 Connect (conn.lowest_layer (), psHost, psPort, yc);
10962 } catch (const std::exception& ex) {
110- ReportIfwCheckResult (
111- yc, checkable, cmdLine, cr,
112- " Can't connect to IfW API on host '" + psHost + " ' port '" + psPort + " ': " + GetUnderstandableError (ex),
113- start
114- );
63+ cr->SetOutput (" Can't connect to IfW API on host '" + psHost + " ' port '" + psPort + " ': " + GetUnderstandableError (ex));
11564 return ;
11665 }
11766
@@ -120,12 +69,7 @@ static void DoIfwNetIo(
12069 try {
12170 sslConn.async_handshake (conn.next_layer ().client , yc);
12271 } catch (const std::exception& ex) {
123- ReportIfwCheckResult (
124- yc, checkable, cmdLine, cr,
125- " TLS handshake with IfW API on host '" + psHost + " ' (SNI: '" + san
126- + " ') port '" + psPort + " ' failed: " + GetUnderstandableError (ex),
127- start
128- );
72+ cr->SetOutput (" TLS handshake with IfW API on host '" + psHost + " ' (SNI: '" + san+ " ') port '" + psPort + " ' failed: " + GetUnderstandableError (ex));
12973 return ;
13074 }
13175
@@ -135,131 +79,93 @@ static void DoIfwNetIo(
13579
13680 try {
13781 cn = GetCertificateCN (cert);
138- } catch (const std::exception&) {
139- }
82+ } catch (const std::exception&) { }
14083
141- ReportIfwCheckResult (
142- yc, checkable, cmdLine, cr,
143- " Certificate validation failed for IfW API on host '" + psHost + " ' (SNI: '" + san + " '; CN: "
144- + (cn.IsString () ? " '" + cn + " '" : " N/A" ) + " ) port '" + psPort + " ': " + sslConn.GetVerifyError (),
145- start
146- );
84+ cr->SetOutput (" Certificate validation failed for IfW API on host '" + psHost + " ' (SNI: '" + san + " '; CN: "
85+ + (cn.IsString () ? " '" + cn + " '" : " N/A" ) + " ) port '" + psPort + " ': " + sslConn.GetVerifyError ());
14786 return ;
14887 }
14988
15089 try {
15190 http::async_write (conn, req, yc);
15291 conn.async_flush (yc);
15392 } catch (const std::exception& ex) {
154- ReportIfwCheckResult (
155- yc, checkable, cmdLine, cr,
156- " Can't send HTTP request to IfW API on host '" + psHost + " ' port '" + psPort + " ': " + GetUnderstandableError (ex),
157- start
158- );
93+ cr->SetOutput (" Can't send HTTP request to IfW API on host '" + psHost + " ' port '" + psPort + " ': " + GetUnderstandableError (ex));
15994 return ;
16095 }
16196
16297 try {
16398 http::async_read (conn, buf, resp, yc);
16499 } catch (const std::exception& ex) {
165- ReportIfwCheckResult (
166- yc, checkable, cmdLine, cr,
167- " Can't read HTTP response from IfW API on host '" + psHost + " ' port '" + psPort + " ': " + GetUnderstandableError (ex),
168- start
169- );
100+ cr->SetOutput (" Can't read HTTP response from IfW API on host '" + psHost + " ' port '" + psPort + " ': " + GetUnderstandableError (ex));
170101 return ;
171102 }
172103
173- double end = Utility::GetTime ();
174-
175104 {
176105 boost::system::error_code ec;
177106 sslConn.async_shutdown (yc[ec]);
178107 }
179108
180- CpuBoundWork cbw (yc);
181109 Value jsonRoot;
182110
183111 try {
184112 jsonRoot = JsonDecode (resp.body ());
185113 } catch (const std::exception& ex) {
186- ReportIfwCheckResult (
187- checkable, cmdLine, cr,
188- " Got bad JSON from IfW API on host '" + psHost + " ' port '" + psPort + " ': " + ex.what (), start, end
189- );
114+ cr->SetOutput (" Got bad JSON from IfW API on host '" + psHost + " ' port '" + psPort + " ': " + ex.what ());
190115 return ;
191116 }
192117
193118 if (!jsonRoot.IsObjectType <Dictionary>()) {
194- ReportIfwCheckResult (
195- checkable, cmdLine, cr,
196- " Got JSON, but not an object, from IfW API on host '"
197- + psHost + " ' port '" + psPort + " ': " + JsonEncode (jsonRoot),
198- start, end
199- );
119+ cr->SetOutput (" Got JSON, but not an object, from IfW API on host '" + psHost + " ' port '" + psPort + " ': " + JsonEncode (jsonRoot));
200120 return ;
201121 }
202122
203123 Value jsonBranch;
204124
205125 if (!Dictionary::Ptr (jsonRoot)->Get (psCommand, &jsonBranch)) {
206- ReportIfwCheckResult (
207- checkable, cmdLine, cr,
208- " Missing ." + psCommand + " in JSON object from IfW API on host '"
209- + psHost + " ' port '" + psPort + " ': " + JsonEncode (jsonRoot),
210- start, end
211- );
126+ cr->SetOutput (" Missing ." + psCommand + " in JSON object from IfW API on host '" + psHost + " ' port '" + psPort + " ': " + JsonEncode (jsonRoot));
212127 return ;
213128 }
214129
215130 if (!jsonBranch.IsObjectType <Dictionary>()) {
216- ReportIfwCheckResult (
217- checkable, cmdLine, cr,
218- " ." + psCommand + " in JSON from IfW API on host '"
219- + psHost + " ' port '" + psPort + " ' is not an object: " + JsonEncode (jsonBranch),
220- start, end
221- );
131+ cr->SetOutput (" ." + psCommand + " in JSON from IfW API on host '" + psHost + " ' port '" + psPort + " ' is not an object: " + JsonEncode (jsonBranch));
222132 return ;
223133 }
224134
225135 Dictionary::Ptr result = jsonBranch;
226136
227- Value exitcode ;
137+ Value rawExitcode ;
228138
229- if (!result->Get (" exitcode" , &exitcode)) {
230- ReportIfwCheckResult (
231- checkable, cmdLine, cr,
139+ if (!result->Get (" exitcode" , &rawExitcode)) {
140+ cr->SetOutput (
232141 " Missing ." + psCommand + " .exitcode in JSON object from IfW API on host '"
233- + psHost + " ' port '" + psPort + " ': " + JsonEncode (result),
234- start, end
142+ + psHost + " ' port '" + psPort + " ': " + JsonEncode (result)
235143 );
236144 return ;
237145 }
238146
239147 static const std::set<double > exitcodes {ServiceOK, ServiceWarning, ServiceCritical, ServiceUnknown};
240148 static const auto exitcodeList (Array::FromSet (exitcodes)->Join (" , " ));
241149
242- if (!exitcode.IsNumber () || exitcodes.find (exitcode) == exitcodes.end ()) {
243- ReportIfwCheckResult (
244- checkable, cmdLine, cr,
245- " Got bad exitcode " + JsonEncode (exitcode) + " from IfW API on host '" + psHost + " ' port '" + psPort
246- + " ', expected one of: " + exitcodeList,
247- start, end
150+ if (!rawExitcode.IsNumber () || exitcodes.find (rawExitcode) == exitcodes.end ()) {
151+ cr->SetOutput (
152+ " Got bad exitcode " + JsonEncode (rawExitcode) + " from IfW API on host '" + psHost + " ' port '" + psPort
153+ + " ', expected one of: " + exitcodeList
248154 );
249155 return ;
250156 }
251157
158+ auto exitcode (static_cast <ServiceState>(rawExitcode.Get <double >()));
159+
252160 auto perfdataVal (result->Get (" perfdata" ));
253161 Array::Ptr perfdata;
254162
255163 try {
256164 perfdata = perfdataVal;
257165 } catch (const std::exception&) {
258- ReportIfwCheckResult (
259- checkable, cmdLine, cr,
166+ cr->SetOutput (
260167 " Got bad perfdata " + JsonEncode (perfdataVal) + " from IfW API on host '"
261- + psHost + " ' port '" + psPort + " ', expected an array" ,
262- start, end
168+ + psHost + " ' port '" + psPort + " ', expected an array"
263169 );
264170 return ;
265171 }
@@ -269,18 +175,20 @@ static void DoIfwNetIo(
269175
270176 for (auto & pv : perfdata) {
271177 if (!pv.IsString ()) {
272- ReportIfwCheckResult (
273- checkable, cmdLine, cr,
178+ cr->SetOutput (
274179 " Got bad perfdata value " + JsonEncode (perfdata) + " from IfW API on host '"
275- + psHost + " ' port '" + psPort + " ', expected an array of strings" ,
276- start, end
180+ + psHost + " ' port '" + psPort + " ', expected an array of strings"
277181 );
278182 return ;
279183 }
280184 }
185+
186+ cr->SetPerformanceData (PluginUtility::SplitPerfdata (perfdata->Join (" " )));
281187 }
282188
283- ReportIfwCheckResult (checkable, cmdLine, cr, result->Get (" checkresult" ), start, end, exitcode, perfdata);
189+ cr->SetState (exitcode);
190+ cr->SetExitStatus (exitcode);
191+ cr->SetOutput (result->Get (" checkresult" ));
284192}
285193
286194void IfwApiCheckTask::ScriptFunc (const Checkable::Ptr& checkable, const CheckResult::Ptr& cr,
@@ -344,6 +252,34 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
344252 String username = resolveMacros (" $ifw_api_username$" );
345253 String password = resolveMacros (" $ifw_api_password$" );
346254
255+ // Use this lambda to process the final Ifw check result. Callers don't need to pass the check result
256+ // as an argument, as the lambda already captures the `cr` and notices all the `cr` changes made across
257+ // the code. You just need to set the necessary cr fields when appropriated and then call this closure.
258+ std::function<void ()> reportResult;
259+
260+ if (auto callback = Checkable::ExecuteCommandProcessFinishedHandler; callback) {
261+ reportResult = [cr, callback = std::move (callback)]() {
262+ ProcessResult pr;
263+ pr.PID = -1 ;
264+ if (auto pd = cr->GetPerformanceData (); pd) {
265+ pr.Output = cr->GetOutput () +" |" + String (pd->Join (" " ));
266+ } else {
267+ pr.Output = cr->GetOutput ();
268+ }
269+ pr.ExecutionStart = cr->GetExecutionStart ();
270+ pr.ExecutionEnd = cr->GetExecutionEnd ();
271+ pr.ExitStatus = cr->GetExitStatus ();
272+
273+ callback (cr->GetCommand (), pr);
274+ };
275+ } else {
276+ reportResult = [checkable, cr]() { checkable->ProcessCheckResult (cr); };
277+ }
278+
279+ // Set the default check result state and exit code to unknown for the moment!
280+ cr->SetExitStatus (ServiceUnknown);
281+ cr->SetState (ServiceUnknown);
282+
347283 Dictionary::Ptr params = new Dictionary ();
348284
349285 if (arguments) {
@@ -369,11 +305,12 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
369305 if (kv.second .GetType () == ValueObject) {
370306 auto now (Utility::GetTime ());
371307
372- ReportIfwCheckResult (
373- checkable, command-> GetName (), cr,
374- " $ifw_api_arguments$ may not directly contain objects (especially functions). " , now, now
375- );
308+ cr-> SetCommand (command-> GetName ());
309+ cr-> SetExecutionStart (now);
310+ cr-> SetExecutionEnd ( now);
311+ cr-> SetOutput ( " $ifw_api_arguments$ may not directly contain objects (especially functions). " );
376312
313+ reportResult ();
377314 return ;
378315 }
379316 }
@@ -498,20 +435,25 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
498435 auto & io (IoEngine::Get ().GetIoContext ());
499436 auto strand (Shared<asio::io_context::strand>::Make (io));
500437 Shared<asio::ssl::context>::Ptr ctx;
501- double start = Utility::GetTime ();
438+
439+ cr->SetExecutionStart (Utility::GetTime ());
440+ cr->SetCommand (cmdLine);
502441
503442 try {
504443 ctx = SetupSslContext (cert, key, ca, crl, DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo ());
505444 } catch (const std::exception& ex) {
506- ReportIfwCheckResult (checkable, cmdLine, cr, ex.what (), start, Utility::GetTime ());
445+ cr->SetOutput (ex.what ());
446+ cr->SetExecutionEnd (Utility::GetTime ());
447+
448+ reportResult ();
507449 return ;
508450 }
509451
510452 auto conn (Shared<AsioTlsStream>::Make (io, *ctx, expectedSan));
511453
512454 IoEngine::SpawnCoroutine (
513455 *strand,
514- [strand, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, conn, req, start, checkTimeout ](asio::yield_context yc) {
456+ [strand, checkable, cr, psCommand, psHost, expectedSan, psPort, conn, req, checkTimeout, reportResult = std::move (reportResult) ](asio::yield_context yc) {
515457 Timeout::Ptr timeout = new Timeout (strand->context (), *strand, boost::posix_time::microseconds (int64_t (checkTimeout * 1e6 )),
516458 [&conn, &checkable](boost::asio::yield_context yc) {
517459 Log (LogNotice, " IfwApiCheckTask" )
@@ -525,7 +467,13 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
525467
526468 Defer cancelTimeout ([&timeout]() { timeout->Cancel (); });
527469
528- DoIfwNetIo (yc, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, *conn, *req, start);
470+ DoIfwNetIo (yc, cr, psCommand, psHost, expectedSan, psPort, *conn, *req);
471+
472+ cr->SetExecutionEnd (Utility::GetTime ());
473+
474+ // Post the check result processing to the global pool not to block the I/O threads,
475+ // which could affect processing important RPC messages and HTTP connections.
476+ Utility::QueueAsyncCallback (reportResult);
529477 }
530478 );
531479}
0 commit comments