Skip to content

Commit

Permalink
IfwApiCheckTask: Use thread-safe check result handler in coroutine
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Aug 30, 2024
1 parent ed40f79 commit c052fa0
Showing 1 changed file with 82 additions and 65 deletions.
147 changes: 82 additions & 65 deletions lib/methods/ifwapichecktask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,39 @@ using namespace icinga;

REGISTER_FUNCTION_NONCONST(Internal, IfwApiCheck, &IfwApiCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros");

static void ReportIfwCheckResult(
const Checkable::Ptr& checkable, const Value& cmdLine, const CheckResult::Ptr& cr,
const String& output, double start, double end, int exitcode = 3, const Array::Ptr& perfdata = nullptr
)
{
using IfwResultHandlerFunc = std::function<void(
const Value& cmdLine, const String& output, double start, double end, int exitcode,
const Array::Ptr& perfdata
)>;

/**
* Get a thread-safe callback of type IfwResultHandlerFunc for processing the final Ifw cr.
*
* Note, it is safe to use the returned callback from a different thread than the one that sets
* the Checkable::ExecuteCommandProcessFinishedHandler but **NOT** this function itself.
*/
inline IfwResultHandlerFunc getIfwResultHandlerFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) {
if (Checkable::ExecuteCommandProcessFinishedHandler) {
ProcessResult pr;
pr.PID = -1;
pr.Output = perfdata ? output + " |" + String(perfdata->Join(" ")) : output;
pr.ExecutionStart = start;
pr.ExecutionEnd = end;
pr.ExitStatus = exitcode;

Checkable::ExecuteCommandProcessFinishedHandler(cmdLine, pr);
} else {
auto callback = Checkable::ExecuteCommandProcessFinishedHandler;

return [callback = std::move(callback)](
const Value& cmdLine, const String& output, double start, double end, int exitcode,
const Array::Ptr& perfdata
) {
ProcessResult pr;
pr.PID = -1;
pr.Output = perfdata ? output + " |" + String(perfdata->Join(" ")) : output;
pr.ExecutionStart = start;
pr.ExecutionEnd = end;
pr.ExitStatus = exitcode;

callback(cmdLine, pr);
};
}

return [checkable, cr](
const Value& cmdLine, const String& output, double start, double end, int exitcode, const Array::Ptr& perfdata
) {
auto splittedPerfdata (perfdata);

if (perfdata) {
Expand All @@ -68,18 +86,17 @@ static void ReportIfwCheckResult(
cr->SetCommand(cmdLine);

checkable->ProcessCheckResult(cr);
}
};
}

static void ReportIfwCheckResult(
boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Value& cmdLine,
const CheckResult::Ptr& cr, const String& output, double start
boost::asio::yield_context yc,const Value& cmdLine, const String& output, double start, const IfwResultHandlerFunc& resultHandler
)
{
double end = Utility::GetTime();
CpuBoundWork cbw (yc);

ReportIfwCheckResult(checkable, cmdLine, cr, output, start, end);
resultHandler(cmdLine, output, start, end, 3, nullptr);
}

static const char* GetUnderstandableError(const std::exception& ex)
Expand All @@ -94,9 +111,9 @@ static const char* GetUnderstandableError(const std::exception& ex)
}

static void DoIfwNetIo(
boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Array::Ptr& cmdLine,
const CheckResult::Ptr& cr, const String& psCommand, const String& psHost, const String& san, const String& psPort,
AsioTlsStream& conn, boost::beast::http::request<boost::beast::http::string_body>& req, double start
boost::asio::yield_context yc, const Array::Ptr& cmdLine, const String& psCommand, const String& psHost, const String& san,
const String& psPort,AsioTlsStream& conn, boost::beast::http::request<boost::beast::http::string_body>& req,
double start, const IfwResultHandlerFunc& resultHandler
)
{
namespace http = boost::beast::http;
Expand All @@ -108,9 +125,9 @@ static void DoIfwNetIo(
Connect(conn.lowest_layer(), psHost, psPort, yc);
} catch (const std::exception& ex) {
ReportIfwCheckResult(
yc, checkable, cmdLine, cr,
yc, cmdLine,
"Can't connect to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex),
start
start, resultHandler
);
return;
}
Expand All @@ -121,10 +138,10 @@ static void DoIfwNetIo(
sslConn.async_handshake(conn.next_layer().client, yc);
} catch (const std::exception& ex) {
ReportIfwCheckResult(
yc, checkable, cmdLine, cr,
yc, cmdLine,
"TLS handshake with IfW API on host '" + psHost + "' (SNI: '" + san
+ "') port '" + psPort + "' failed: " + GetUnderstandableError(ex),
start
start, resultHandler
);
return;
}
Expand All @@ -139,10 +156,10 @@ static void DoIfwNetIo(
}

ReportIfwCheckResult(
yc, checkable, cmdLine, cr,
yc, cmdLine,
"Certificate validation failed for IfW API on host '" + psHost + "' (SNI: '" + san + "'; CN: "
+ (cn.IsString() ? "'" + cn + "'" : "N/A") + ") port '" + psPort + "': " + sslConn.GetVerifyError(),
start
start, resultHandler
);
return;
}
Expand All @@ -152,9 +169,9 @@ static void DoIfwNetIo(
conn.async_flush(yc);
} catch (const std::exception& ex) {
ReportIfwCheckResult(
yc, checkable, cmdLine, cr,
yc, cmdLine,
"Can't send HTTP request to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex),
start
start, resultHandler
);
return;
}
Expand All @@ -163,9 +180,9 @@ static void DoIfwNetIo(
http::async_read(conn, buf, resp, yc);
} catch (const std::exception& ex) {
ReportIfwCheckResult(
yc, checkable, cmdLine, cr,
yc, cmdLine,
"Can't read HTTP response from IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex),
start
start, resultHandler
);
return;
}
Expand All @@ -183,41 +200,38 @@ static void DoIfwNetIo(
try {
jsonRoot = JsonDecode(resp.body());
} catch (const std::exception& ex) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
"Got bad JSON from IfW API on host '" + psHost + "' port '" + psPort + "': " + ex.what(), start, end
);
resultHandler(
cmdLine,"Got bad JSON from IfW API on host '" + psHost + "' port '" + psPort + "': " + ex.what(), start, end, 3, nullptr);
return;
}

if (!jsonRoot.IsObjectType<Dictionary>()) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
"Got JSON, but not an object, from IfW API on host '"
+ psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot),
start, end
resultHandler(
cmdLine,
"Got JSON, but not an object, from IfW API on host '"+ psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot),
start, end, 3, nullptr
);
return;
}

Value jsonBranch;

if (!Dictionary::Ptr(jsonRoot)->Get(psCommand, &jsonBranch)) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
resultHandler(
cmdLine,
"Missing ." + psCommand + " in JSON object from IfW API on host '"
+ psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot),
start, end
start, end, 3, nullptr
);
return;
}

if (!jsonBranch.IsObjectType<Dictionary>()) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
resultHandler(
cmdLine,
"." + psCommand + " in JSON from IfW API on host '"
+ psHost + "' port '" + psPort + "' is not an object: " + JsonEncode(jsonBranch),
start, end
start, end, 3, nullptr
);
return;
}
Expand All @@ -227,11 +241,11 @@ static void DoIfwNetIo(
Value exitcode;

if (!result->Get("exitcode", &exitcode)) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
resultHandler(
cmdLine,
"Missing ." + psCommand + ".exitcode in JSON object from IfW API on host '"
+ psHost + "' port '" + psPort + "': " + JsonEncode(result),
start, end
start, end, 3, nullptr
);
return;
}
Expand All @@ -240,11 +254,11 @@ static void DoIfwNetIo(
static const auto exitcodeList (Array::FromSet(exitcodes)->Join(", "));

if (!exitcode.IsNumber() || exitcodes.find(exitcode) == exitcodes.end()) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
resultHandler(
cmdLine,
"Got bad exitcode " + JsonEncode(exitcode) + " from IfW API on host '" + psHost + "' port '" + psPort
+ "', expected one of: " + exitcodeList,
start, end
start, end, 3, nullptr
);
return;
}
Expand All @@ -255,11 +269,11 @@ static void DoIfwNetIo(
try {
perfdata = perfdataVal;
} catch (const std::exception&) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
resultHandler(
cmdLine,
"Got bad perfdata " + JsonEncode(perfdataVal) + " from IfW API on host '"
+ psHost + "' port '" + psPort + "', expected an array",
start, end
start, end, 3, nullptr
);
return;
}
Expand All @@ -269,18 +283,18 @@ static void DoIfwNetIo(

for (auto& pv : perfdata) {
if (!pv.IsString()) {
ReportIfwCheckResult(
checkable, cmdLine, cr,
resultHandler(
cmdLine,
"Got bad perfdata value " + JsonEncode(perfdata) + " from IfW API on host '"
+ psHost + "' port '" + psPort + "', expected an array of strings",
start, end
start, end, 3, nullptr
);
return;
}
}
}

ReportIfwCheckResult(checkable, cmdLine, cr, result->Get("checkresult"), start, end, exitcode, perfdata);
resultHandler(cmdLine, result->Get("checkresult"), start, end, exitcode, perfdata);
}

void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr,
Expand Down Expand Up @@ -344,6 +358,8 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
String username = resolveMacros("$ifw_api_username$");
String password = resolveMacros("$ifw_api_password$");

IfwResultHandlerFunc resultHandler = getIfwResultHandlerFunc(checkable, cr);

Dictionary::Ptr params = new Dictionary();

if (arguments) {
Expand All @@ -369,9 +385,10 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
if (kv.second.GetType() == ValueObject) {
auto now (Utility::GetTime());

ReportIfwCheckResult(
checkable, command->GetName(), cr,
"$ifw_api_arguments$ may not directly contain objects (especially functions).", now, now
resultHandler(
command->GetName(),
"$ifw_api_arguments$ may not directly contain objects (especially functions).",
now, now, 3, nullptr
);

return;
Expand Down Expand Up @@ -503,15 +520,15 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
try {
ctx = SetupSslContext(cert, key, ca, crl, DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo());
} catch (const std::exception& ex) {
ReportIfwCheckResult(checkable, cmdLine, cr, ex.what(), start, Utility::GetTime());
resultHandler(cmdLine, ex.what(), start, Utility::GetTime(), 3, nullptr);
return;
}

auto conn (Shared<AsioTlsStream>::Make(io, *ctx, expectedSan));

IoEngine::SpawnCoroutine(
*strand,
[strand, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, conn, req, start, checkTimeout](asio::yield_context yc) {
[strand, checkable, cmdLine, psCommand, psHost, expectedSan, psPort, conn, req, start, checkTimeout, handler = std::move(resultHandler)](asio::yield_context yc) {
Timeout::Ptr timeout = new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)),
[&conn, &checkable](boost::asio::yield_context yc) {
Log(LogNotice, "IfwApiCheckTask")
Expand All @@ -525,7 +542,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes

Defer cancelTimeout ([&timeout]() { timeout->Cancel(); });

DoIfwNetIo(yc, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, *conn, *req, start);
DoIfwNetIo(yc, cmdLine, psCommand, psHost, expectedSan, psPort, *conn, *req, start, handler);
}
);
}

0 comments on commit c052fa0

Please sign in to comment.