Quality: Comments and logs in cluster config sync

This commit is contained in:
Michael Friedrich 2019-06-07 11:32:27 +02:00
parent 3852c51c9f
commit 577e42e137

View File

@ -21,8 +21,8 @@ REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler);
boost::mutex ApiListener::m_ConfigSyncStageLock; boost::mutex ApiListener::m_ConfigSyncStageLock;
/** /**
* Entrypoint for updating all authoritative configs into var/lib/icinga2/api/zones * Entrypoint for updating all authoritative configs from /etc/zones.d, packages, etc.
* * into var/lib/icinga2/api/zones
*/ */
void ApiListener::SyncLocalZoneDirs() const void ApiListener::SyncLocalZoneDirs() const
{ {
@ -36,7 +36,7 @@ void ApiListener::SyncLocalZoneDirs() const
} }
/** /**
* Sync a zone directory where we have an authoritative copy (zones.d, etc.) * Sync a zone directory where we have an authoritative copy (zones.d, packages, etc.)
* *
* This function collects the registered zone config dirs from * This function collects the registered zone config dirs from
* the config compiler and reads the file content into the config * the config compiler and reads the file content into the config
@ -58,25 +58,27 @@ void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const
String zoneName = zone->GetName(); String zoneName = zone->GetName();
/* Load registered zone paths, e.g. '_etc', '_api' and user packages. */ // Load registered zone paths, e.g. '_etc', '_api' and user packages.
for (const ZoneFragment& zf : ConfigCompiler::GetZoneDirs(zoneName)) { for (const ZoneFragment& zf : ConfigCompiler::GetZoneDirs(zoneName)) {
ConfigDirInformation newConfigPart = LoadConfigDir(zf.Path); ConfigDirInformation newConfigPart = LoadConfigDir(zf.Path);
/* Config files '*.conf'. */ // Config files '*.conf'.
{ {
ObjectLock olock(newConfigPart.UpdateV1); ObjectLock olock(newConfigPart.UpdateV1);
for (const Dictionary::Pair& kv : newConfigPart.UpdateV1) { for (const Dictionary::Pair& kv : newConfigPart.UpdateV1) {
String path = "/" + zf.Tag + kv.first; String path = "/" + zf.Tag + kv.first;
newConfigInfo.UpdateV1->Set(path, kv.second); newConfigInfo.UpdateV1->Set(path, kv.second);
newConfigInfo.Checksums->Set(path, GetChecksum(kv.second)); newConfigInfo.Checksums->Set(path, GetChecksum(kv.second));
} }
} }
/* Meta files. */ // Meta files.
{ {
ObjectLock olock(newConfigPart.UpdateV2); ObjectLock olock(newConfigPart.UpdateV2);
for (const Dictionary::Pair& kv : newConfigPart.UpdateV2) { for (const Dictionary::Pair& kv : newConfigPart.UpdateV2) {
String path = "/" + zf.Tag + kv.first; String path = "/" + zf.Tag + kv.first;
newConfigInfo.UpdateV2->Set(path, kv.second); newConfigInfo.UpdateV2->Set(path, kv.second);
newConfigInfo.Checksums->Set(path, GetChecksum(kv.second)); newConfigInfo.Checksums->Set(path, GetChecksum(kv.second));
} }
@ -85,6 +87,7 @@ void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const
size_t sumUpdates = newConfigInfo.UpdateV1->GetLength() + newConfigInfo.UpdateV2->GetLength(); size_t sumUpdates = newConfigInfo.UpdateV1->GetLength() + newConfigInfo.UpdateV2->GetLength();
// Return early if there are no updates.
if (sumUpdates == 0) if (sumUpdates == 0)
return; return;
@ -93,13 +96,13 @@ void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Copying " << sumUpdates << " zone configuration files for zone '" << zoneName << "' to '" << productionZonesDir << "'."; << "Copying " << sumUpdates << " zone configuration files for zone '" << zoneName << "' to '" << productionZonesDir << "'.";
/* Purge files to allow deletion via zones.d. */ // Purge files to allow deletion via zones.d.
if (Utility::PathExists(productionZonesDir)) if (Utility::PathExists(productionZonesDir))
Utility::RemoveDirRecursive(productionZonesDir); Utility::RemoveDirRecursive(productionZonesDir);
Utility::MkDirP(productionZonesDir, 0700); Utility::MkDirP(productionZonesDir, 0700);
/* Copy content and add additional meta data. */ // Copy content and add additional meta data.
size_t numBytes = 0; size_t numBytes = 0;
/* Note: We cannot simply copy directories here. /* Note: We cannot simply copy directories here.
@ -111,15 +114,19 @@ void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const
{ {
ObjectLock olock(newConfig); ObjectLock olock(newConfig);
for (const Dictionary::Pair& kv : newConfig) { for (const Dictionary::Pair& kv : newConfig) {
String dst = productionZonesDir + "/" + kv.first; String dst = productionZonesDir + "/" + kv.first;
Utility::MkDirP(Utility::DirName(dst), 0755); Utility::MkDirP(Utility::DirName(dst), 0755);
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Updating configuration file: " << dst; << "Updating configuration file: " << dst;
String content = kv.second; String content = kv.second;
std::ofstream fp(dst.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); std::ofstream fp(dst.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fp << content; fp << content;
fp.close(); fp.close();
@ -127,11 +134,12 @@ void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const
} }
} }
/* Additional metadata. */ // Additional metadata.
String tsPath = productionZonesDir + "/.timestamp"; String tsPath = productionZonesDir + "/.timestamp";
if (!Utility::PathExists(tsPath)) { if (!Utility::PathExists(tsPath)) {
std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc); std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
fp << std::fixed << Utility::GetTime(); fp << std::fixed << Utility::GetTime();
fp.close(); fp.close();
} }
@ -143,12 +151,14 @@ void ApiListener::SyncLocalZoneDir(const Zone::Ptr& zone) const
fp.close(); fp.close();
} }
// Checksums.
String checksumsPath = productionZonesDir + "/.checksums"; String checksumsPath = productionZonesDir + "/.checksums";
if (Utility::PathExists(checksumsPath)) if (Utility::PathExists(checksumsPath))
Utility::Remove(checksumsPath); Utility::Remove(checksumsPath);
std::ofstream fp(checksumsPath.CStr(), std::ofstream::out | std::ostream::trunc); std::ofstream fp(checksumsPath.CStr(), std::ofstream::out | std::ostream::trunc);
fp << std::fixed << JsonEncode(newConfigInfo.Checksums); fp << std::fixed << JsonEncode(newConfigInfo.Checksums);
fp.close(); fp.close();
@ -173,13 +183,13 @@ void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
Zone::Ptr clientZone = endpoint->GetZone(); Zone::Ptr clientZone = endpoint->GetZone();
Zone::Ptr localZone = Zone::GetLocalZone(); Zone::Ptr localZone = Zone::GetLocalZone();
/* don't try to send config updates to our master */ // Don't send config updates to parent zones
if (!clientZone->IsChildOf(localZone)) if (!clientZone->IsChildOf(localZone))
return; return;
Dictionary::Ptr configUpdateV1 = new Dictionary(); Dictionary::Ptr configUpdateV1 = new Dictionary();
Dictionary::Ptr configUpdateV2 = new Dictionary(); Dictionary::Ptr configUpdateV2 = new Dictionary();
Dictionary::Ptr configUpdateChecksums = new Dictionary(); /* new since 2.11 */ Dictionary::Ptr configUpdateChecksums = new Dictionary(); // new since 2.11
String zonesDir = GetApiZonesDir(); String zonesDir = GetApiZonesDir();
@ -187,11 +197,11 @@ void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
String zoneName = zone->GetName(); String zoneName = zone->GetName();
String zoneDir = zonesDir + zoneName; String zoneDir = zonesDir + zoneName;
/* Only sync child and global zones. */ // Only sync child and global zones.
if (!zone->IsChildOf(clientZone) && !zone->IsGlobal()) if (!zone->IsChildOf(clientZone) && !zone->IsGlobal())
continue; continue;
/* Zone was configured, but there's no configuration directory. */ // Zone was configured, but there's no configuration directory.
if (!Utility::PathExists(zoneDir)) if (!Utility::PathExists(zoneDir))
continue; continue;
@ -203,7 +213,7 @@ void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
configUpdateV1->Set(zoneName, config.UpdateV1); configUpdateV1->Set(zoneName, config.UpdateV1);
configUpdateV2->Set(zoneName, config.UpdateV2); configUpdateV2->Set(zoneName, config.UpdateV2);
configUpdateChecksums->Set(zoneName, config.Checksums); /* new since 2.11 */ configUpdateChecksums->Set(zoneName, config.Checksums); // new since 2.11
} }
Dictionary::Ptr message = new Dictionary({ Dictionary::Ptr message = new Dictionary({
@ -211,8 +221,8 @@ void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
{ "method", "config::Update" }, { "method", "config::Update" },
{ "params", new Dictionary({ { "params", new Dictionary({
{ "update", configUpdateV1 }, { "update", configUpdateV1 },
{ "update_v2", configUpdateV2 }, /* Since 2.4.2. */ { "update_v2", configUpdateV2 }, // Since 2.4.2.
{ "checksums", configUpdateChecksums } /* Since 2.11.0. */ { "checksums", configUpdateChecksums } // Since 2.11.0.
}) } }) }
}); });
@ -233,7 +243,7 @@ void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
*/ */
Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
{ {
/* Verify permissions and trust relationship. */ // Verify permissions and trust relationship.
if (!origin->FromClient->GetEndpoint() || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) if (!origin->FromClient->GetEndpoint() || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone)))
return Empty; return Empty;
@ -263,12 +273,12 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
<< "Applying config update from endpoint '" << fromEndpointName << "Applying config update from endpoint '" << fromEndpointName
<< "' of zone '" << fromZoneName << "'."; << "' of zone '" << fromZoneName << "'.";
/* Config files. */ // Config files.
Dictionary::Ptr updateV1 = params->Get("update"); Dictionary::Ptr updateV1 = params->Get("update");
/* Meta data files: .timestamp, etc. */ // Meta data files: .timestamp, etc.
Dictionary::Ptr updateV2 = params->Get("update_v2"); Dictionary::Ptr updateV2 = params->Get("update_v2");
/* New since 2.11.0. */ // New since 2.11.0.
Dictionary::Ptr checksums; Dictionary::Ptr checksums;
if (params->Contains("checksums")) if (params->Contains("checksums"))
@ -276,7 +286,7 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
bool configChange = false; bool configChange = false;
/* Keep track of the relative config paths for later validation and copying. */ // Keep track of the relative config paths for later validation and copying. TODO: Find a better algorithm.
std::vector<String> relativePaths; std::vector<String> relativePaths;
/* /*
@ -290,11 +300,12 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
Utility::MkDirP(apiZonesStageDir, 0700); Utility::MkDirP(apiZonesStageDir, 0700);
/* Analyse and process the update. */ // Analyse and process the update.
ObjectLock olock(updateV1); ObjectLock olock(updateV1);
for (const Dictionary::Pair& kv : updateV1) { for (const Dictionary::Pair& kv : updateV1) {
/* Check for the configured zones. */ // Check for the configured zones.
String zoneName = kv.first; String zoneName = kv.first;
Zone::Ptr zone = Zone::GetByName(zoneName); Zone::Ptr zone = Zone::GetByName(zoneName);
@ -302,39 +313,42 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
Log(LogWarning, "ApiListener") Log(LogWarning, "ApiListener")
<< "Ignoring config update from endpoint '" << fromEndpointName << "Ignoring config update from endpoint '" << fromEndpointName
<< "' for unknown zone '" << zoneName << "'."; << "' for unknown zone '" << zoneName << "'.";
continue; continue;
} }
/* Whether we already have configuration in zones.d. */ // Ignore updates where we have an authoritive copy in etc/zones.d, packages, etc.
if (ConfigCompiler::HasZoneConfigAuthority(zoneName)) { if (ConfigCompiler::HasZoneConfigAuthority(zoneName)) {
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Ignoring config update from endpoint '" << fromEndpointName << "Ignoring config update from endpoint '" << fromEndpointName
<< "' for zone '" << zoneName << "' because we have an authoritative version of the zone's config."; << "' for zone '" << zoneName << "' because we have an authoritative version of the zone's config.";
continue; continue;
} }
/* Put the received configuration into our stage directory. */ // Put the received configuration into our stage directory.
String productionConfigZoneDir = GetApiZonesDir() + zoneName; String productionConfigZoneDir = GetApiZonesDir() + zoneName;
String stageConfigZoneDir = GetApiZonesStageDir() + zoneName; String stageConfigZoneDir = GetApiZonesStageDir() + zoneName;
Utility::MkDirP(productionConfigZoneDir, 0700); Utility::MkDirP(productionConfigZoneDir, 0700);
Utility::MkDirP(stageConfigZoneDir, 0700); Utility::MkDirP(stageConfigZoneDir, 0700);
/* Merge the config information. */ // Merge the config information.
ConfigDirInformation newConfigInfo; ConfigDirInformation newConfigInfo;
newConfigInfo.UpdateV1 = kv.second; newConfigInfo.UpdateV1 = kv.second;
/* Load metadata. */ // Load metadata.
if (updateV2) if (updateV2)
newConfigInfo.UpdateV2 = updateV2->Get(kv.first); newConfigInfo.UpdateV2 = updateV2->Get(kv.first);
/* Load checksums. New since 2.11. */ // Load checksums. New since 2.11.
if (checksums) if (checksums)
newConfigInfo.Checksums = checksums->Get(kv.first); newConfigInfo.Checksums = checksums->Get(kv.first);
/* Load the current production config details. */ // Load the current production config details.
ConfigDirInformation productionConfigInfo = LoadConfigDir(productionConfigZoneDir); ConfigDirInformation productionConfigInfo = LoadConfigDir(productionConfigZoneDir);
// Merge updateV1 and updateV2
Dictionary::Ptr productionConfig = MergeConfigUpdate(productionConfigInfo); Dictionary::Ptr productionConfig = MergeConfigUpdate(productionConfigInfo);
Dictionary::Ptr newConfig = MergeConfigUpdate(newConfigInfo); Dictionary::Ptr newConfig = MergeConfigUpdate(newConfigInfo);
@ -346,11 +360,21 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
<< "Received configuration for zone '" << zoneName << "' from endpoint '" << "Received configuration for zone '" << zoneName << "' from endpoint '"
<< fromEndpointName << "'. Comparing the checksums."; << fromEndpointName << "'. Comparing the checksums.";
/* TODO: Do this earlier in hello-handshakes. */ // TODO: Do this earlier in hello-handshakes?
if (CheckConfigChange(productionConfigInfo, newConfigInfo)) if (CheckConfigChange(productionConfigInfo, newConfigInfo))
configChange = true; configChange = true;
} else { } else {
/* TODO: Figure out whether we always need to rely on the timestamp flags when there are checksums involved. */ /* Fallback to timestamp handling when the parent endpoint didn't send checks.
* This can happen when the satellite is 2.11 and the master is 2.10.
*
* TODO: Deprecate and remove this behaviour in 2.13+.
*/
Log(LogWarning, "ApiListener")
<< "Received configuration update without checksums from parent endpoint "
<< fromEndpointName << ". This behaviour is deprecated. Please upgrade the parent endpoint to 2.11+";
double productionTimestamp; double productionTimestamp;
if (!productionConfig->Contains("/.timestamp")) if (!productionConfig->Contains("/.timestamp"))
@ -365,8 +389,9 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
else else
newTimestamp = newConfig->Get("/.timestamp"); newTimestamp = newConfig->Get("/.timestamp");
/* skip update if our configuration files are more recent */ // Skip update if our configuration files are more recent.
if (productionTimestamp >= newTimestamp) { if (productionTimestamp >= newTimestamp) {
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Our configuration is more recent than the received configuration update." << "Our configuration is more recent than the received configuration update."
<< " Ignoring configuration file update for path '" << stageConfigZoneDir << "'. Current timestamp '" << " Ignoring configuration file update for path '" << stageConfigZoneDir << "'. Current timestamp '"
@ -375,21 +400,26 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
<< ") >= received timestamp '" << ") >= received timestamp '"
<< Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' (" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' ("
<< newTimestamp << ")."; << newTimestamp << ").";
} else { } else {
configChange = true; configChange = true;
} }
/* Keep another hack when there's a timestamp file missing. */ // Keep another hack when there's a timestamp file missing.
{
ObjectLock olock(newConfig); ObjectLock olock(newConfig);
for (const Dictionary::Pair &kv : newConfig) { for (const Dictionary::Pair &kv : newConfig) {
/* This is super expensive with a string content comparison. */
// This is super expensive with a string content comparison.
if (productionConfig->Get(kv.first) != kv.second) { if (productionConfig->Get(kv.first) != kv.second) {
if (!Utility::Match("*/.timestamp", kv.first)) if (!Utility::Match("*/.timestamp", kv.first))
configChange = true; configChange = true;
} }
} }
}
/* Update the .timestamp file. */ // Update the .timestamp file.
String tsPath = stageConfigZoneDir + "/.timestamp"; String tsPath = stageConfigZoneDir + "/.timestamp";
if (!Utility::PathExists(tsPath)) { if (!Utility::PathExists(tsPath)) {
std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc); std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
@ -398,17 +428,20 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
} }
} }
/* Dump the received configuration for this zone into the stage directory. */ // Dump the received configuration for this zone into the stage directory.
size_t numBytes = 0; size_t numBytes = 0;
{ {
ObjectLock olock(newConfig); ObjectLock olock(newConfig);
for (const Dictionary::Pair& kv : newConfig) { for (const Dictionary::Pair& kv : newConfig) {
/* Store the relative config file path for later validation and activation. /* Store the relative config file path for later validation and activation.
* IMPORTANT: Store this prior to any filters. */ * IMPORTANT: Store this prior to any filters.
* */
relativePaths.push_back(zoneName + "/" + kv.first); relativePaths.push_back(zoneName + "/" + kv.first);
/* Ignore same config content. This is an expensive comparison. */ // Ignore same config content. This is an expensive comparison.
if (productionConfig->Get(kv.first) == kv.second) if (productionConfig->Get(kv.first) == kv.second)
continue; continue;
@ -417,11 +450,13 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Stage: Updating received configuration file '" << path << "' for zone '" << zoneName << "'."; << "Stage: Updating received configuration file '" << path << "' for zone '" << zoneName << "'.";
/* Sync string content only. */ // Sync string content only.
String content = kv.second; String content = kv.second;
/* Generate a directory tree (zones/1/2/3 might not exist yet). */ // Generate a directory tree (zones/1/2/3 might not exist yet).
Utility::MkDirP(Utility::DirName(path), 0755); Utility::MkDirP(Utility::DirName(path), 0755);
// Write the content to file.
std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fp << content; fp << content;
fp.close(); fp.close();
@ -434,9 +469,10 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
<< "Applying configuration file update for path '" << stageConfigZoneDir << "' (" << "Applying configuration file update for path '" << stageConfigZoneDir << "' ("
<< numBytes << " Bytes)."; << numBytes << " Bytes).";
/* If the update removes a path, delete it on disk and signal a config change. */ // If the update removes a path, delete it on disk and signal a config change.
{ {
ObjectLock xlock(productionConfig); ObjectLock xlock(productionConfig);
for (const Dictionary::Pair& kv : productionConfig) { for (const Dictionary::Pair& kv : productionConfig) {
if (!newConfig->Contains(kv.first)) { if (!newConfig->Contains(kv.first)) {
configChange = true; configChange = true;
@ -450,9 +486,11 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
/* /*
* We have processed all configuration files and stored them in the staging directory. * We have processed all configuration files and stored them in the staging directory.
*
* We need to store them locally for later analysis. A config change means * We need to store them locally for later analysis. A config change means
* that we will validate the configuration in a separate process sandbox, * that we will validate the configuration in a separate process sandbox,
* and only copy the configuration to production when everything is ok. * and only copy the configuration to production when everything is ok.
*
* A successful validation also triggers the final restart. * A successful validation also triggers the final restart.
*/ */
if (configChange) { if (configChange) {
@ -494,18 +532,18 @@ void ApiListener::TryActivateZonesStageCallback(const ProcessResult& pr,
fpStatus << pr.ExitStatus; fpStatus << pr.ExitStatus;
fpStatus.close(); fpStatus.close();
/* validation went fine, copy stage and reload */ // Validation went fine, copy stage and reload.
if (pr.ExitStatus == 0) { if (pr.ExitStatus == 0) {
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Config validation for stage '" << apiZonesStageDir << "' was OK, replacing into '" << apiZonesDir << "' and triggering reload."; << "Config validation for stage '" << apiZonesStageDir << "' was OK, replacing into '" << apiZonesDir << "' and triggering reload.";
/* Purge production before copying stage. */ // Purge production before copying stage.
if (Utility::PathExists(apiZonesDir)) if (Utility::PathExists(apiZonesDir))
Utility::RemoveDirRecursive(apiZonesDir); Utility::RemoveDirRecursive(apiZonesDir);
Utility::MkDirP(apiZonesDir, 0700); Utility::MkDirP(apiZonesDir, 0700);
/* Copy all synced configuration files from stage to production. */ // Copy all synced configuration files from stage to production.
for (const String& path : relativePaths) { for (const String& path : relativePaths) {
Log(LogInformation, "ApiListener") Log(LogInformation, "ApiListener")
<< "Copying file '" << path << "' from config sync staging to production zones directory."; << "Copying file '" << path << "' from config sync staging to production zones directory.";
@ -518,6 +556,7 @@ void ApiListener::TryActivateZonesStageCallback(const ProcessResult& pr,
Utility::CopyFile(stagePath, currentPath); Utility::CopyFile(stagePath, currentPath);
} }
// Clear any failed deployment before
ApiListener::Ptr listener = ApiListener::GetInstance(); ApiListener::Ptr listener = ApiListener::GetInstance();
if (listener) if (listener)
@ -525,10 +564,11 @@ void ApiListener::TryActivateZonesStageCallback(const ProcessResult& pr,
Application::RequestRestart(); Application::RequestRestart();
// All good, return early.
return; return;
} }
/* Error case. */ // Error case.
Log(LogCritical, "ApiListener") Log(LogCritical, "ApiListener")
<< "Config validation failed for staged cluster config sync in '" << apiZonesStageDir << "Config validation failed for staged cluster config sync in '" << apiZonesStageDir
<< "'. Aborting. Logs: '" << logFile << "'"; << "'. Aborting. Logs: '" << logFile << "'";
@ -565,7 +605,7 @@ void ApiListener::AsyncTryActivateZonesStage(const std::vector<String>& relative
args->Add("--validate"); args->Add("--validate");
/* Set the ZonesStageDir. This creates our own local chroot without any additional automated zone includes. */ // Set the ZonesStageDir. This creates our own local chroot without any additional automated zone includes.
args->Add("--define"); args->Add("--define");
args->Add("System.ZonesStageVarDir=" + GetApiZonesStageDir()); args->Add("System.ZonesStageVarDir=" + GetApiZonesStageDir());
@ -615,39 +655,43 @@ bool ApiListener::CheckConfigChange(const ConfigDirInformation& oldConfig, const
Dictionary::Ptr oldChecksums = oldConfig.Checksums; Dictionary::Ptr oldChecksums = oldConfig.Checksums;
Dictionary::Ptr newChecksums = newConfig.Checksums; Dictionary::Ptr newChecksums = newConfig.Checksums;
Log(LogCritical, "ApiListener") // TODO: Figure out whether normal users need this for debugging.
<< "Comparing old (" << oldChecksums->GetLength() << "): '" Log(LogDebug, "ApiListener")
<< "Checking for config change between stage and production. Old (" << oldChecksums->GetLength() << "): '"
<< JsonEncode(oldChecksums) << JsonEncode(oldChecksums)
<< "' to new (" << newChecksums->GetLength() << "): '" << "' vs. new (" << newChecksums->GetLength() << "): '"
<< JsonEncode(newChecksums) << "'."; << JsonEncode(newChecksums) << "'.";
/* Different length means that either one or the other side added or removed something. */ // Different length means that either one or the other side added or removed something. */
if (oldChecksums->GetLength() != newChecksums->GetLength()) if (oldChecksums->GetLength() != newChecksums->GetLength())
return true; return true;
/* Both dictionaries have an equal size. */ // Both dictionaries have an equal size.
ObjectLock olock(oldChecksums); ObjectLock olock(oldChecksums);
for (const Dictionary::Pair& kv : oldChecksums) { for (const Dictionary::Pair& kv : oldChecksums) {
String path = kv.first; String path = kv.first;
String oldChecksum = kv.second; String oldChecksum = kv.second;
/* Only use configuration files for checksum calculation. */
// TODO: Figure out if config changes only apply to '.conf'. Leaving this open for other config files.
//if (!Utility::Match("*.conf", path)) //if (!Utility::Match("*.conf", path))
// continue; // continue;
/* Ignore internal files, especially .timestamp and .checksums. /* Ignore internal files, especially .timestamp and .checksums.
*
* If we don't, this results in "always change" restart loops. * If we don't, this results in "always change" restart loops.
*/ */
if (Utility::Match("/.*", path)) if (Utility::Match("/.*", path))
continue; continue;
Log(LogCritical, "ApiListener") Log(LogDebug, "ApiListener")
<< "Checking " << path << " for checksum: " << oldChecksum; << "Checking " << path << " for checksum: " << oldChecksum;
/* Check whether our key exists in the new checksums, and they have an equal value. */ // Check whether our key exists in the new checksums, and they have an equal value.
String newChecksum = newChecksums->Get(path); String newChecksum = newChecksums->Get(path);
if (newChecksums->Get(path) != kv.second) { if (newChecksums->Get(path) != kv.second) {
Log(LogCritical, "ApiListener") Log(LogDebug, "ApiListener")
<< "Path '" << path << "' doesn't match old checksum '" << "Path '" << path << "' doesn't match old checksum '"
<< newChecksum << "' with new checksum '" << oldChecksum << "'."; << newChecksum << "' with new checksum '" << oldChecksum << "'.";
return true; return true;
@ -684,7 +728,7 @@ ConfigDirInformation ApiListener::LoadConfigDir(const String& dir)
*/ */
void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file) void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file)
{ {
/* Avoid loading the authoritative marker for syncs. */ // Avoid loading the authoritative marker for syncs at all cost.
if (Utility::BaseName(file) == ".authoritative") if (Utility::BaseName(file) == ".authoritative")
return; return;
@ -716,7 +760,7 @@ void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String&
/* Calculate a checksum for each file (and a global one later). /* Calculate a checksum for each file (and a global one later).
* *
* IMPORTANT: Ignore the .authoritative file, this will not be synced. * IMPORTANT: Ignore the .authoritative file above, this must not be synced.
* */ * */
config.Checksums->Set(relativePath, GetChecksum(content)); config.Checksums->Set(relativePath, GetChecksum(content));
} }
@ -739,4 +783,3 @@ Dictionary::Ptr ApiListener::MergeConfigUpdate(const ConfigDirInformation& confi
return result; return result;
} }