diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index 728acfc5975..4b3b58a4e87 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -1288,6 +1288,7 @@ params | Dictionary Key | Type | Description ---------------------|-------------|------------------ +capabilities | Number | Bitmask, see `lib/remote/apilistener.hpp`. version | Number | Icinga 2 version, e.g. 21300 for v2.13.0. ##### Functions diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp index 67c3990c068..73a28d95069 100644 --- a/lib/icinga/apiactions.cpp +++ b/lib/icinga/apiactions.cpp @@ -821,11 +821,11 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, std::set endpoints = zone->GetEndpoints(); for (const Endpoint::Ptr& childEndpoint : endpoints) { - if (childEndpoint->GetIcingaVersion() < 21300) { + 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() + "' has version < 2.13."); + 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"); diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp index 56e8ef976d4..69a5d698428 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -641,7 +641,7 @@ Value ClusterEvents::ExecuteCommandAPIHandler(const MessageOrigin::Ptr& origin, std::set endpoints = zone->GetEndpoints(); for (const Endpoint::Ptr &childEndpoint : endpoints) { - if (childEndpoint->GetIcingaVersion() < 21300) { + if (!(childEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand)) { double now = Utility::GetTime(); Dictionary::Ptr executedParams = new Dictionary(); executedParams->Set("execution", params->Get("source")); @@ -650,7 +650,7 @@ Value ClusterEvents::ExecuteCommandAPIHandler(const MessageOrigin::Ptr& origin, executedParams->Set("service", params->Get("service")); executedParams->Set("exit", 126); executedParams->Set("output", - "Endpoint '" + childEndpoint->GetName() + "' has version < 2.13."); + "Endpoint '" + childEndpoint->GetName() + "' doesn't support executing arbitrary commands."); executedParams->Set("start", now); executedParams->Set("end", now); diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 49ed2919886..7ce07c38d2c 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -522,6 +522,8 @@ static const auto l_AppVersionInt (([]() -> unsigned long { + boost::lexical_cast(match[3].str()); })()); +static const auto l_MyCapabilities (ApiCapabilities::ExecuteArbitraryCommand); + /** * Processes a new client connection. * @@ -667,7 +669,8 @@ void ApiListener::NewClientHandlerInternal( { "jsonrpc", "2.0" }, { "method", "icinga::Hello" }, { "params", new Dictionary({ - { "version", (double)l_AppVersionInt } + { "version", (double)l_AppVersionInt }, + { "capabilities", (double)l_MyCapabilities } }) } }), yc); @@ -705,7 +708,8 @@ void ApiListener::NewClientHandlerInternal( { "jsonrpc", "2.0" }, { "method", "icinga::Hello" }, { "params", new Dictionary({ - { "version", (double)l_AppVersionInt } + { "version", (double)l_AppVersionInt }, + { "capabilities", (double)l_MyCapabilities } }) } }), yc); @@ -1643,6 +1647,7 @@ Value ApiListener::HelloAPIHandler(const MessageOrigin::Ptr& origin, const Dicti if (endpoint) { endpoint->SetIcingaVersion((double)params->Get("version")); + endpoint->SetCapabilities((double)params->Get("capabilities")); } } } diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index 4a3623a6895..a5f4618a68b 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace icinga @@ -38,6 +39,35 @@ struct ConfigDirInformation Dictionary::Ptr Checksums; }; +/** + * If the version reported by icinga::Hello is not enough to tell whether + * the peer has a specific capability, add the latter to this bitmask. + * + * Note that due to the capability exchange via JSON-RPC and the state storage via JSON + * the bitmask numbers are stored in IEEE 754 64-bit floats. + * The latter have 53 digit bits which limit the bitmask. + * Not to run out of bits: + * + * Once all Icinga versions which don't have a specific capability are completely EOL, + * remove the respective capability checks and assume the peer has the capability. + * Once all Icinga versions which still check for the capability are completely EOL, + * remove the respective bit from icinga::Hello. + * Once all Icinga versions which still have the respective bit in icinga::Hello + * are completely EOL, remove the bit here. + * Once all Icinga versions which still have the respective bit here + * are completely EOL, feel free to re-use the bit. + * + * completely EOL = not supported, even if an important customer of us used it and + * not expected to appear in a multi-level cluster, e.g. a 4 level cluster with + * v2.11 -> v2.10 -> v2.9 -> v2.8 - v2.7 isn't here + * + * @ingroup remote + */ +enum class ApiCapabilities : uint_fast64_t +{ + ExecuteArbitraryCommand = 1u +}; + /** * @ingroup remote */ diff --git a/lib/remote/endpoint.ti b/lib/remote/endpoint.ti index 31a00b89d1c..78551ecf0dd 100644 --- a/lib/remote/endpoint.ti +++ b/lib/remote/endpoint.ti @@ -1,6 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/configobject.hpp" +#include library remote; @@ -24,6 +25,9 @@ class Endpoint : ConfigObject [state] "unsigned long" icinga_version { default {{{ return 0; }}} }; + [state] uint_fast64_t capabilities { + default {{{ return 0; }}} + }; [no_user_modify] bool connecting; [no_user_modify] bool syncing;