From 18a7388117a0bf6cf9877fb00ffa05fbc983eeed Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 8 Sep 2020 15:54:10 +0200 Subject: [PATCH] config::HaveFiles: send config::WantFiles if not up-to-date refs #8210 --- lib/remote/apilistener-filesync.cpp | 123 ++++++++++++++++++++++++++++ lib/remote/apilistener.hpp | 1 + 2 files changed, 124 insertions(+) diff --git a/lib/remote/apilistener-filesync.cpp b/lib/remote/apilistener-filesync.cpp index ce51d16e5..9331e08ee 100644 --- a/lib/remote/apilistener-filesync.cpp +++ b/lib/remote/apilistener-filesync.cpp @@ -24,6 +24,7 @@ using namespace icinga; REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler); REGISTER_APIFUNCTION(HaveZones, config, &ApiListener::ConfigHaveZonesHandler); REGISTER_APIFUNCTION(WantZones, config, &ApiListener::ConfigWantZonesHandler); +REGISTER_APIFUNCTION(HaveFiles, config, &ApiListener::ConfigHaveFilesHandler); boost::mutex ApiListener::m_ConfigSyncStageLock; @@ -551,6 +552,128 @@ Value ApiListener::ConfigHaveZonesHandler(const MessageOrigin::Ptr& origin, cons return Empty; } +Value ApiListener::ConfigHaveFilesHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) +{ + auto endpoint (origin->FromClient->GetEndpoint()); + + // Verify permissions and trust relationship. + if (!endpoint || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) { + return Empty; + } + + ApiListener::Ptr listener = ApiListener::GetInstance(); + + if (!listener) { + Log(LogCritical, "ApiListener", "No instance available."); + return Empty; + } + + if (!listener->GetAcceptConfig()) { + Log(LogWarning, "ApiListener") + << "Ignoring information about files in zones. '" << listener->GetName() << "' does not accept config."; + return Empty; + } + + auto fromEndpointName (origin->FromClient->GetEndpoint()->GetName()); + auto fromZoneName (GetFromZoneName(origin->FromZone)); + + Log(LogInformation, "ApiListener") + << "Checking information about files in zones from endpoint '" << fromEndpointName + << "' of zone '" << fromZoneName << "'."; + + Dictionary::Ptr checksums = params->Get("checksums"); + auto zonesDir (GetApiZonesDir()); + Dictionary::Ptr want = new Dictionary(); + ObjectLock oLock (checksums); + + for (auto& kv : checksums) { + if (!Zone::GetByName(kv.first)) { + Log(LogWarning, "ApiListener") + << "Ignoring information about files in unknown zone '" << kv.first + << "' from endpoint '" << fromEndpointName << "'."; + continue; + } + + // Ignore files declarations where we have an authoritive copy in etc/zones.d, packages, etc. + if (ConfigCompiler::HasZoneConfigAuthority(kv.first)) { + Log(LogInformation, "ApiListener") + << "Ignoring information about files in zone '" << kv.first << "' from endpoint '" + << fromEndpointName << "' because we have an authoritative version of the zone's config."; + continue; + } + + String checksumsPath = zonesDir + kv.first + "/.checksums"; + Dictionary::Ptr checksums; + std::ifstream fp (checksumsPath.CStr(), std::ifstream::binary); + + if (fp) { + checksums = JsonDecode(String( + std::istreambuf_iterator(fp), std::istreambuf_iterator() + )); + } else { + checksums = new Dictionary(); + } + + Dictionary::Ptr havePerZone = kv.second; + Array::Ptr wantPerZone = new Array(); + ObjectLock oLock (havePerZone); + + for (auto& kv : havePerZone) { + if (kv.second != checksums->Get(kv.first)) { + wantPerZone->Add(kv.first); + } + + checksums->Remove(kv.first); + } + + Log(LogNotice, "ApiListener") + << "Compared to the information from endpoint '" << fromEndpointName + << "' about files in zone '" << kv.first << "' there are " << wantPerZone->GetLength() + << " file(s) to fetch the content(s) of and " << checksums->GetLength() << " file(s) to remove."; + + if (!wantPerZone->GetLength() && !checksums->GetLength()) { + continue; + } + + want->Set(kv.first, wantPerZone); + } + + if (!want->GetLength()) { + Log(LogNotice, "ApiListener") + << "Compared to the information about files in zones from endpoint '" + << fromEndpointName << "' 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 files in zones from endpoint '" + << fromEndpointName << "' not everything is up-to-date, but we're not connected."; + return Empty; + } + + Log(LogInformation, "ApiListener") + << "Compared to the information about files in zones from endpoint '" << fromEndpointName + << "' not everything is up-to-date. Requesting files of " << want->GetLength() << " zone(s)."; + + client->SendMessage(new Dictionary({ + { "jsonrpc", "2.0" }, + { "method", "config::WantFiles" }, + { "params", new Dictionary({ + { "files", want } + }) } + })); + + return Empty; +} + Value ApiListener::ConfigWantZonesHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) { Log(LogNotice, "ApiListener") diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index db781abff..eab1a1b0f 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -87,6 +87,7 @@ public: static Value ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); static Value ConfigHaveZonesHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); static Value ConfigWantZonesHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); + static Value ConfigHaveFilesHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); static void HandleConfigUpdate(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); /* configsync */