From fcc1799a5de532ffb183aae382b7104fab677b26 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Thu, 25 Oct 2018 14:10:30 +0200 Subject: [PATCH] Split config file sync updates, part I This commit also introduces a playground for checksums, whilst refactoring the code in large parts. --- lib/remote/apilistener-filesync.cpp | 677 ++++++++++++++++------------ lib/remote/apilistener.cpp | 2 +- lib/remote/apilistener.hpp | 14 +- 3 files changed, 389 insertions(+), 304 deletions(-) diff --git a/lib/remote/apilistener-filesync.cpp b/lib/remote/apilistener-filesync.cpp index a65617318..cec19e580 100644 --- a/lib/remote/apilistener-filesync.cpp +++ b/lib/remote/apilistener-filesync.cpp @@ -3,6 +3,8 @@ #include "remote/apilistener.hpp" #include "remote/apifunction.hpp" #include "config/configcompiler.hpp" +#include "base/tlsutility.hpp" +#include "base/json.hpp" #include "base/configtype.hpp" #include "base/logger.hpp" #include "base/convert.hpp" @@ -18,73 +20,312 @@ REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler); boost::mutex ApiListener::m_ConfigSyncStageLock; /** - * Read the given file and store it in the config information structure. - * Callback function for Glob(). + * Entrypoint for updating all authoritative configs into var/lib/icinga2/api/zones * - * @param config Reference to the config information object. - * @param path File path. - * @param file Full file name. */ -void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file) +void ApiListener::SyncLocalZoneDirs() const { - CONTEXT("Creating config update for file '" + file + "'"); + for (const Zone::Ptr& zone : ConfigType::GetObjectsByType()) { + try { + SyncLocalZoneDir(zone); + } catch (const std::exception&) { + continue; + } + } +} - Log(LogNotice, "ApiListener") - << "Creating config update for file '" << file << "'."; - - std::ifstream fp(file.CStr(), std::ifstream::binary); - if (!fp) +/** + * Sync a zone directory where we have an authoritative copy (zones.d, etc.) + * + * This function collects the registered zone config dirs from + * the config compiler and reads the file content into the config + * information structure. + * + * Returns early when there are no updates. + * + * @param zone Pointer to the zone object being synced. + */ +void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const +{ + if (!zone) return; - String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); + ConfigDirInformation newConfigInfo; + newConfigInfo.UpdateV1 = new Dictionary(); + newConfigInfo.UpdateV2 = new Dictionary(); + newConfigInfo.Checksums = new Dictionary(); - Dictionary::Ptr update; + String zoneName = zone->GetName(); + + /* Load registered zone paths, e.g. '_etc', '_api' and user packages. */ + for (const ZoneFragment& zf : ConfigCompiler::GetZoneDirs(zoneName)) { + ConfigDirInformation newConfigPart = LoadConfigDir(zf.Path); + + /* Config files '*.conf'. */ + { + ObjectLock olock(newConfigPart.UpdateV1); + for (const Dictionary::Pair& kv : newConfigPart.UpdateV1) { + String path = "/" + zf.Tag + kv.first; + newConfigInfo.UpdateV1->Set(path, kv.second); + newConfigInfo.Checksums->Set(path, GetChecksum(kv.second)); + } + } + + /* Meta files. */ + { + ObjectLock olock(newConfigPart.UpdateV2); + for (const Dictionary::Pair& kv : newConfigPart.UpdateV2) { + String path = "/" + zf.Tag + kv.first; + newConfigInfo.UpdateV2->Set(path, kv.second); + newConfigInfo.Checksums->Set(path, GetChecksum(kv.second)); + } + } + } + + int sumUpdates = newConfigInfo.UpdateV1->GetLength() + newConfigInfo.UpdateV2->GetLength(); + + if (sumUpdates == 0) + return; + + String productionZonesDir = GetApiZonesDir() + zoneName; + + Log(LogInformation, "ApiListener") + << "Copying " << sumUpdates << " zone configuration files for zone '" << zoneName << "' to '" << productionZonesDir << "'."; + + /* Purge files to allow deletion via zones.d. */ + if (Utility::PathExists(productionZonesDir)) + Utility::RemoveDirRecursive(productionZonesDir); + + Utility::MkDirP(productionZonesDir, 0700); + + /* Copy content and add additional meta data. */ + size_t numBytes = 0; + + /* Note: We cannot simply copy directories here. + * + * Zone directories are registered from everywhere and we already + * have read their content into memory with LoadConfigDir(). + */ + Dictionary::Ptr newConfig = MergeConfigUpdate(newConfigInfo); + + { + ObjectLock olock(newConfig); + for (const Dictionary::Pair& kv : newConfig) { + String dst = productionZonesDir + "/" + kv.first; + Utility::MkDirP(Utility::DirName(dst), 0755); + + Log(LogInformation, "ApiListener") + << "Updating configuration file: " << dst; + + String content = kv.second; + std::ofstream fp(dst.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); + fp << content; + fp.close(); + + numBytes += content.GetLength(); + } + } + + /* Additional metadata. */ + String tsPath = productionZonesDir + "/.timestamp"; + + if (!Utility::PathExists(tsPath)) { + std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc); + fp << std::fixed << Utility::GetTime(); + fp.close(); + } + + String authPath = productionZonesDir + "/.authoritative"; + + if (!Utility::PathExists(authPath)) { + std::ofstream fp(authPath.CStr(), std::ofstream::out | std::ostream::trunc); + } + + String checksumsPath = productionZonesDir + "/.checksums"; + + if (Utility::PathExists(checksumsPath)) + (void) unlink(checksumsPath.CStr()); + + std::ofstream fp(checksumsPath.CStr(), std::ofstream::out | std::ostream::trunc); + fp << std::fixed << JsonEncode(newConfigInfo.Checksums); + fp.close(); +} + +/** + * Entrypoint for sending a file based config update to a cluster client. + * This includes security checks for zone relations. + * Loads the zone config files where this client belongs to + * and sends the 'config::Update' JSON-RPC message. + * + * @param aclient Connected JSON-RPC client. + */ +void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient) +{ + Endpoint::Ptr endpoint = aclient->GetEndpoint(); + ASSERT(endpoint); + + Zone::Ptr azone = endpoint->GetZone(); + Zone::Ptr lzone = Zone::GetLocalZone(); + + /* don't try to send config updates to our master */ + if (!azone->IsChildOf(lzone)) + return; + + Dictionary::Ptr configUpdateV1 = new Dictionary(); + Dictionary::Ptr configUpdateV2 = new Dictionary(); + Dictionary::Ptr configUpdateChecksums = new Dictionary(); + + String zonesDir = GetApiZonesDir(); + + for (const Zone::Ptr& zone : ConfigType::GetObjectsByType()) { + String zoneName = zone->GetName(); + String zoneDir = zonesDir + zoneName; + + if (!zone->IsChildOf(azone) && !zone->IsGlobal()) + continue; + + if (!Utility::PathExists(zoneDir)) + continue; + + Log(LogInformation, "ApiListener") + << "Syncing configuration files for " << (zone->IsGlobal() ? "global " : "") + << "zone '" << zoneName << "' to endpoint '" << endpoint->GetName() << "'."; + + ConfigDirInformation config = LoadConfigDir(zoneDir); + + configUpdateV1->Set(zoneName, config.UpdateV1); + configUpdateV2->Set(zoneName, config.UpdateV2); + configUpdateChecksums->Set(zoneName, config.Checksums); + } + + Dictionary::Ptr message = new Dictionary({ + { "jsonrpc", "2.0" }, + { "method", "config::Update" }, + { "params", new Dictionary({ + { "update", configUpdateV1 }, + { "update_v2", configUpdateV2 }, /* Since 2.4.2. */ + { "checksums", configUpdateChecksums } /* Since 2.11.0. */ + }) } + }); + + aclient->SendMessage(message); +} + +/** + * Registered handler when a new config::Update message is received. + * + * Checks destination and permissions first, then analyses the update. + * The newly received configuration is not copied to production immediately, + * but into the staging directory first. + * Last, the async validation and restart is triggered. + * + * @param origin Where this message came from. + * @param params Message parameters including the config updates. + * @returns Empty, required by the interface. + */ +Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) +{ + /* Verify permissions and trust relationship. */ + if (!origin->FromClient->GetEndpoint() || (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 config update. '" << listener->GetName() << "' does not accept config."; + return Empty; + } + + /* Only one transaction is allowed, concurrent message handlers need to wait. + * This affects two parent endpoints sending the config in the same moment. + */ + boost::mutex::scoped_lock lock(m_ConfigSyncStageLock); + + String fromEndpointName = origin->FromClient->GetEndpoint()->GetName(); + String fromZoneName = GetFromZoneName(origin->FromZone); + + Log(LogInformation, "ApiListener") + << "Applying config update from endpoint '" << fromEndpointName + << "' of zone '" << fromZoneName << "'."; + + Dictionary::Ptr updateV1 = params->Get("update"); + Dictionary::Ptr updateV2 = params->Get("update_v2"); + + bool configChange = false; + std::vector relativePaths; /* - * 'update' messages contain conf files. 'update_v2' syncs everything else (.timestamp). - * - * **Keep this intact to stay compatible with older clients.** + * We can and must safely purge the staging directory, as the difference is taken between + * runtime production config and newly received configuration. */ - if (Utility::Match("*.conf", file)) - update = config.UpdateV1; - else - update = config.UpdateV2; + String apiZonesStageDir = GetApiZonesStageDir(); - update->Set(file.SubStr(path.GetLength()), content); -} + if (Utility::PathExists(apiZonesStageDir)) + Utility::RemoveDirRecursive(apiZonesStageDir); -/** - * Compatibility helper for merging config update v1 and v2 into a global result. - * - * @param config Config information structure. - * @returns Dictionary which holds the merged information. - */ -Dictionary::Ptr ApiListener::MergeConfigUpdate(const ConfigDirInformation& config) -{ - Dictionary::Ptr result = new Dictionary(); + Utility::MkDirP(apiZonesStageDir, 0700); - if (config.UpdateV1) - config.UpdateV1->CopyTo(result); + /* Analyse and process the update. */ + ObjectLock olock(updateV1); + for (const Dictionary::Pair& kv : updateV1) { - if (config.UpdateV2) - config.UpdateV2->CopyTo(result); + /* Check for the configured zones. */ + String zoneName = kv.first; + Zone::Ptr zone = Zone::GetByName(zoneName); - return result; -} + if (!zone) { + Log(LogWarning, "ApiListener") + << "Ignoring config update from endpoint '" << fromEndpointName + << "' for unknown zone '" << zoneName << "'."; + continue; + } -/** - * Load the given config dir and read their file content into the config structure. - * - * @param dir Path to the config directory. - * @returns ConfigInformation structure. - */ -ConfigDirInformation ApiListener::LoadConfigDir(const String& dir) -{ - ConfigDirInformation config; - config.UpdateV1 = new Dictionary(); - config.UpdateV2 = new Dictionary(); - Utility::GlobRecursive(dir, "*", std::bind(&ApiListener::ConfigGlobHandler, std::ref(config), dir, _1), GlobFile); - return config; + /* Whether we already have configuration in zones.d. */ + if (ConfigCompiler::HasZoneConfigAuthority(zoneName)) { + Log(LogInformation, "ApiListener") + << "Ignoring config update from endpoint '" << fromEndpointName + << "' for zone '" << zoneName << "' because we have an authoritative version of the zone's config."; + continue; + } + + /* Put the received configuration into our stage directory. */ + String currentConfigDir = GetApiZonesDir() + zoneName; + String stageConfigDir = GetApiZonesStageDir() + zoneName; + + Utility::MkDirP(currentConfigDir, 0700); + Utility::MkDirP(stageConfigDir, 0700); + + /* Merge the config information. */ + ConfigDirInformation newConfigInfo; + newConfigInfo.UpdateV1 = kv.second; + + if (updateV2) + newConfigInfo.UpdateV2 = updateV2->Get(kv.first); + + /* Load the current production config details. */ + ConfigDirInformation currentConfigInfo = LoadConfigDir(currentConfigDir); + + /* Diff the current production configuration with the received configuration. + * If there was a change, collect a signal for later stage validation. + */ + if (UpdateConfigDir(currentConfigInfo, newConfigInfo, stageConfigDir, zoneName, relativePaths, false)) + configChange = true; + } + + if (configChange) { + /* Spawn a validation process. On success, move the staged configuration + * into production and restart. + */ + AsyncTryActivateZonesStage(relativePaths); + } + + return Empty; } /** @@ -210,253 +451,6 @@ bool ApiListener::UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, con return configChange; } -/** - * Sync a zone directory where we have an authoritative copy (zones.d, etc.) - * - * This function collects the registered zone config dirs from - * the config compiler and reads the file content into the config - * information structure. - * - * Returns early when there are no updates. - * - * @param zone Pointer to the zone object being synced. - */ -void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const -{ - if (!zone) - return; - - ConfigDirInformation newConfigInfo; - newConfigInfo.UpdateV1 = new Dictionary(); - newConfigInfo.UpdateV2 = new Dictionary(); - - String zoneName = zone->GetName(); - - for (const ZoneFragment& zf : ConfigCompiler::GetZoneDirs(zoneName)) { - ConfigDirInformation newConfigPart = LoadConfigDir(zf.Path); - - { - ObjectLock olock(newConfigPart.UpdateV1); - for (const Dictionary::Pair& kv : newConfigPart.UpdateV1) { - newConfigInfo.UpdateV1->Set("/" + zf.Tag + kv.first, kv.second); - } - } - - { - ObjectLock olock(newConfigPart.UpdateV2); - for (const Dictionary::Pair& kv : newConfigPart.UpdateV2) { - newConfigInfo.UpdateV2->Set("/" + zf.Tag + kv.first, kv.second); - } - } - } - - int sumUpdates = newConfigInfo.UpdateV1->GetLength() + newConfigInfo.UpdateV2->GetLength(); - - if (sumUpdates == 0) - return; - - String currentDir = Configuration::DataDir + "/api/zones/" + zoneName; - - Log(LogInformation, "ApiListener") - << "Copying " << sumUpdates << " zone configuration files for zone '" << zoneName << "' to '" << currentDir << "'."; - - ConfigDirInformation oldConfigInfo = LoadConfigDir(currentDir); - - /* Purge files to allow deletion via zones.d. */ - Utility::RemoveDirRecursive(currentDir); - Utility::MkDirP(currentDir, 0700); - - std::vector relativePaths; - UpdateConfigDir(oldConfigInfo, newConfigInfo, currentDir, zoneName, relativePaths, true); -} - -/** - * Entrypoint for updating all authoritative configs into var/lib/icinga2/api/zones - * - */ -void ApiListener::SyncZoneDirs() const -{ - for (const Zone::Ptr& zone : ConfigType::GetObjectsByType()) { - try { - SyncZoneDir(zone); - } catch (const std::exception&) { - continue; - } - } -} - -/** - * Entrypoint for sending a file based config update to a cluster client. - * This includes security checks for zone relations. - * Loads the zone config files where this client belongs to - * and sends the 'config::Update' JSON-RPC message. - * - * @param aclient Connected JSON-RPC client. - */ -void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient) -{ - Endpoint::Ptr endpoint = aclient->GetEndpoint(); - ASSERT(endpoint); - - Zone::Ptr azone = endpoint->GetZone(); - Zone::Ptr lzone = Zone::GetLocalZone(); - - /* don't try to send config updates to our master */ - if (!azone->IsChildOf(lzone)) - return; - - Dictionary::Ptr configUpdateV1 = new Dictionary(); - Dictionary::Ptr configUpdateV2 = new Dictionary(); - - String zonesDir = Configuration::DataDir + "/api/zones"; - - for (const Zone::Ptr& zone : ConfigType::GetObjectsByType()) { - String zoneDir = zonesDir + "/" + zone->GetName(); - - if (!zone->IsChildOf(azone) && !zone->IsGlobal()) - continue; - - if (!Utility::PathExists(zoneDir)) - continue; - - Log(LogInformation, "ApiListener") - << "Syncing configuration files for " << (zone->IsGlobal() ? "global " : "") - << "zone '" << zone->GetName() << "' to endpoint '" << endpoint->GetName() << "'."; - - ConfigDirInformation config = LoadConfigDir(zonesDir + "/" + zone->GetName()); - configUpdateV1->Set(zone->GetName(), config.UpdateV1); - configUpdateV2->Set(zone->GetName(), config.UpdateV2); - } - - Dictionary::Ptr message = new Dictionary({ - { "jsonrpc", "2.0" }, - { "method", "config::Update" }, - { "params", new Dictionary({ - { "update", configUpdateV1 }, - { "update_v2", configUpdateV2 } - }) } - }); - - aclient->SendMessage(message); -} - -/** - * Registered handler when a new config::Update message is received. - * - * Checks destination and permissions first, then analyses the update. - * The newly received configuration is not copied to production immediately, - * but into the staging directory first. - * Last, the async validation and restart is triggered. - * - * @param origin Where this message came from. - * @param params Message parameters including the config updates. - * @returns Empty, required by the interface. - */ -Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) -{ - /* Verify permissions and trust relationship. */ - if (!origin->FromClient->GetEndpoint() || (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 config update. '" << listener->GetName() << "' does not accept config."; - return Empty; - } - - /* Only one transaction is allowed, concurrent message handlers need to wait. - * This affects two parent endpoints sending the config in the same moment. - */ - boost::mutex::scoped_lock lock(m_ConfigSyncStageLock); - - String fromEndpointName = origin->FromClient->GetEndpoint()->GetName(); - String fromZoneName = GetFromZoneName(origin->FromZone); - - Log(LogInformation, "ApiListener") - << "Applying config update from endpoint '" << fromEndpointName - << "' of zone '" << fromZoneName << "'."; - - Dictionary::Ptr updateV1 = params->Get("update"); - Dictionary::Ptr updateV2 = params->Get("update_v2"); - - bool configChange = false; - std::vector relativePaths; - - /* - * We can and must safely purge the staging directory, as the difference is taken between - * runtime production config and newly received configuration. - */ - String apiZonesStageDir = GetApiZonesStageDir(); - - if (Utility::PathExists(apiZonesStageDir)) - Utility::RemoveDirRecursive(apiZonesStageDir); - - Utility::MkDirP(apiZonesStageDir, 0700); - - /* Analyse and process the update. */ - ObjectLock olock(updateV1); - for (const Dictionary::Pair& kv : updateV1) { - - /* Check for the configured zones. */ - String zoneName = kv.first; - Zone::Ptr zone = Zone::GetByName(zoneName); - - if (!zone) { - Log(LogWarning, "ApiListener") - << "Ignoring config update from endpoint '" << fromEndpointName - << "' for unknown zone '" << zoneName << "'."; - continue; - } - - /* Whether we already have configuration in zones.d. */ - if (ConfigCompiler::HasZoneConfigAuthority(zoneName)) { - Log(LogInformation, "ApiListener") - << "Ignoring config update from endpoint '" << fromEndpointName - << "' for zone '" << zoneName << "' because we have an authoritative version of the zone's config."; - continue; - } - - /* Put the received configuration into our stage directory. */ - String currentConfigDir = GetApiZonesDir() + zoneName; - String stageConfigDir = GetApiZonesStageDir() + zoneName; - - Utility::MkDirP(currentConfigDir, 0700); - Utility::MkDirP(stageConfigDir, 0700); - - /* Merge the config information. */ - ConfigDirInformation newConfigInfo; - newConfigInfo.UpdateV1 = kv.second; - - if (updateV2) - newConfigInfo.UpdateV2 = updateV2->Get(kv.first); - - /* Load the current production config details. */ - ConfigDirInformation currentConfigInfo = LoadConfigDir(currentConfigDir); - - /* Diff the current production configuration with the received configuration. - * If there was a change, collect a signal for later stage validation. - */ - if (UpdateConfigDir(currentConfigInfo, newConfigInfo, stageConfigDir, zoneName, relativePaths, false)) - configChange = true; - } - - if (configChange) { - /* Spawn a validation process. On success, move the staged configuration - * into production and restart. - */ - AsyncTryActivateZonesStage(relativePaths); - } - - return Empty; -} - /** * Callback for stage config validation. * When validation was successful, the configuration is copied from @@ -586,3 +580,90 @@ void ApiListener::ClearLastFailedZonesStageValidation() { SetLastFailedZonesStageValidation(Dictionary::Ptr()); } + +/** + * Generate a config checksum. + * + * @param + */ +String ApiListener::GetChecksum(const String& content) +{ + return SHA256(content); +} + +/** + * Load the given config dir and read their file content into the config structure. + * + * @param dir Path to the config directory. + * @returns ConfigInformation structure. + */ +ConfigDirInformation ApiListener::LoadConfigDir(const String& dir) +{ + ConfigDirInformation config; + config.UpdateV1 = new Dictionary(); + config.UpdateV2 = new Dictionary(); + config.Checksums = new Dictionary(); + + Utility::GlobRecursive(dir, "*", std::bind(&ApiListener::ConfigGlobHandler, std::ref(config), dir, _1), GlobFile); + return config; +} + +/** + * Read the given file and store it in the config information structure. + * Callback function for Glob(). + * + * @param config Reference to the config information object. + * @param path File path. + * @param file Full file name. + */ +void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file) +{ + CONTEXT("Creating config update for file '" + file + "'"); + + Log(LogNotice, "ApiListener") + << "Creating config update for file '" << file << "'."; + + std::ifstream fp(file.CStr(), std::ifstream::binary); + if (!fp) + return; + + String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); + + Dictionary::Ptr update; + String relativePath = file.SubStr(path.GetLength()); + + /* + * 'update' messages contain conf files. 'update_v2' syncs everything else (.timestamp). + * + * **Keep this intact to stay compatible with older clients.** + */ + if (Utility::Match("*.conf", file)) + update = config.UpdateV1; + else + update = config.UpdateV2; + + update->Set(relativePath, content); + + /* Calculate a checksum for each file (and a global one later). */ + config.Checksums->Set(relativePath, GetChecksum(content)); +} + +/** + * Compatibility helper for merging config update v1 and v2 into a global result. + * + * @param config Config information structure. + * @returns Dictionary which holds the merged information. + */ +Dictionary::Ptr ApiListener::MergeConfigUpdate(const ConfigDirInformation& config) +{ + Dictionary::Ptr result = new Dictionary(); + + if (config.UpdateV1) + config.UpdateV1->CopyTo(result); + + if (config.UpdateV2) + config.UpdateV2->CopyTo(result); + + return result; +} + diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index b9f8b9780..661a4eaaf 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -242,7 +242,7 @@ void ApiListener::Start(bool runtimeCreated) Log(LogInformation, "ApiListener") << "'" << GetName() << "' started."; - SyncZoneDirs(); + SyncLocalZoneDirs(); ObjectImpl::Start(runtimeCreated); diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index 3d1082444..f9d92848f 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -32,6 +32,7 @@ struct ConfigDirInformation { Dictionary::Ptr UpdateV1; Dictionary::Ptr UpdateV2; + Dictionary::Ptr Checksums; }; /** @@ -170,21 +171,24 @@ private: /* filesync */ static boost::mutex m_ConfigSyncStageLock; - static ConfigDirInformation LoadConfigDir(const String& dir); + void SyncLocalZoneDirs() const; + void SyncLocalZoneDir(const Zone::Ptr& zone) const; + + void SendConfigUpdate(const JsonRpcConnection::Ptr& aclient); + static Dictionary::Ptr MergeConfigUpdate(const ConfigDirInformation& config); static bool UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, const ConfigDirInformation& newConfigInfo, const String& configDir, const String& zoneName, std::vector& relativePaths, bool authoritative); - void SyncZoneDirs() const; - void SyncZoneDir(const Zone::Ptr& zone) const; - + static ConfigDirInformation LoadConfigDir(const String& dir); static void ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file); - void SendConfigUpdate(const JsonRpcConnection::Ptr& aclient); static void TryActivateZonesStageCallback(const ProcessResult& pr, const std::vector& relativePaths); static void AsyncTryActivateZonesStage(const std::vector& relativePaths); + static String GetChecksum(const String& content); + void UpdateLastFailedZonesStageValidation(const String& log); void ClearLastFailedZonesStageValidation();