diff --git a/lib/remote/apilistener-configsync.cpp b/lib/remote/apilistener-configsync.cpp index c34cdf42e..ab2adb82f 100644 --- a/lib/remote/apilistener-configsync.cpp +++ b/lib/remote/apilistener-configsync.cpp @@ -14,6 +14,7 @@ using namespace icinga; REGISTER_APIFUNCTION(UpdateObject, config, &ApiListener::ConfigUpdateObjectAPIHandler); REGISTER_APIFUNCTION(DeleteObject, config, &ApiListener::ConfigDeleteObjectAPIHandler); +REGISTER_APIFUNCTION(HaveObjects, config, &ApiListener::ConfigHaveObjectsAPIHandler); INITIALIZE_ONCE([]() { ConfigObject::OnActiveChanged.connect(&ApiListener::ConfigUpdateObjectHandler); @@ -194,6 +195,133 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin return Empty; } +Value ApiListener::ConfigHaveObjectsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) +{ + Log(LogNotice, "ApiListener") + << "Received information about runtime objects: " << JsonEncode(params); + + /* check permissions */ + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) + return Empty; + + Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint(); + String identity = origin->FromClient->GetIdentity(); + + /* discard messages if the client is not configured on this node */ + if (!endpoint) { + Log(LogNotice, "ApiListener") + << "Discarding 'config have objects' message from '" << identity << "': Invalid endpoint origin (client not allowed)."; + return Empty; + } + + Zone::Ptr endpointZone = endpoint->GetZone(); + + /* discard messages if the sender is in a child zone */ + if (!Zone::GetLocalZone()->IsChildOf(endpointZone)) { + Log(LogNotice, "ApiListener") + << "Discarding 'config have objects' message" + << " from '" << identity << "' (endpoint: '" << endpoint->GetName() << "', zone: '" << endpointZone->GetName() + << "'). Sender is in a child zone."; + return Empty; + } + + /* ignore messages if the endpoint does not accept config */ + if (!listener->GetAcceptConfig()) { + Log(LogWarning, "ApiListener") + << "Ignoring information about runtime objects" + << " from '" << identity << "' (endpoint: '" << endpoint->GetName() << "', zone: '" << endpointZone->GetName() + << "'). '" << listener->GetName() << "' does not accept config."; + return Empty; + } + + Dictionary::Ptr versions (params->Get("versions")); + Dictionary::Ptr want = new Dictionary(); + ObjectLock oLock (versions); + + for (auto& kv : versions) { + auto type (Type::GetByName(kv.first)); + + if (!type) { + Log(LogWarning, "ApiListener") + << "Bad object type in information about runtime objects from '" + << identity << "': '" << kv.first << "'"; + continue; + } + + auto ctype (dynamic_cast(type.get())); + + if (!ctype) { + Log(LogWarning, "ApiListener") + << "Bad object type in information about runtime objects from '" + << identity << "': '" << kv.first << "'"; + continue; + } + + Dictionary::Ptr perType (kv.second); + ObjectLock oLock (perType); + + for (auto& kv : perType) { + auto object (ctype->GetObject(kv.first)); + + if (object && object->GetVersion() >= (double)kv.second) { + Log(LogNotice, "ApiListener") + << "Compared to the information about runtime objects from '" << identity << "' the " + << type->GetName() << " '" << object->GetName() << "' is up-to-date."; + continue; + } + + Log(LogNotice, "ApiListener") + << "Compared to the information about runtime objects from '" << identity << "' the " << type->GetName() + << " '" << kv.first << "' is " << (object ? "outdated" : "missing") << "."; + + Array::Ptr perType = want->Get(type->GetName()); + + if (!perType) { + perType = new Array(); + want->Set(type->GetName(), perType); + } + + perType->Add(kv.first); + } + } + + if (!want->GetLength()) { + Log(LogNotice, "ApiListener") + << "Compared to the information about runtime objects from '" << identity << "' everything is up-to-date."; + return Empty; + } + + JsonRpcConnection::Ptr client; + + for (auto& conn : endpoint->GetClients()) { + client = conn; + break; + } + + if (!client) { + Log(LogNotice, "ApiListener") + << "Compared to the information about runtime objects from '" << identity + << "' not everything is up-to-date, but '" << identity << "' is not connected."; + return Empty; + } + + Log(LogInformation, "ApiListener") + << "Compared to the information about runtime objects from '" << identity + << "' not everything is up-to-date. Requesting updates."; + + client->SendMessage(new Dictionary({ + { "jsonrpc", "2.0" }, + { "method", "config::WantObjects" }, + { "params", new Dictionary({ + { "objects", want } + }) } + })); + + return Empty; +} + Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) { Log(LogNotice, "ApiListener") diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index ad957093a..d79f8f195 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -91,6 +91,7 @@ public: static void ConfigUpdateObjectHandler(const ConfigObject::Ptr& object, const Value& cookie); static Value ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); static Value ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); + static Value ConfigHaveObjectsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); /* API config packages */ void SetActivePackageStage(const String& package, const String& stage);