boost::mutex ApiListener::m_ConfigSyncStageLock;
+/**
+ * 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 + "'");
Dictionary::Ptr update;
+ /*
+ * '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->Set(file.SubStr(path.GetLength()), 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();
return result;
}
+/**
+ * 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;
return config;
}
+/**
+ * Diffs the old current configuration with the new configuration
+ * and copies the collected content. Detects whether a change
+ * happened, this is used for later restarts.
+ *
+ * This generic function is called in two situations:
+ * - Local zones.d to var/lib/api/zones copy on the master (authoritative: true)
+ * - Received config update on a cluster node (authoritative: false)
+ *
+ * @param oldConfigInfo Config information struct for the current old deployed config.
+ * @param newConfigInfo Config information struct for the received synced config.
+ * @param configDir Destination for copying new files (production, or stage dir).
+ * @param zoneName Currently processed zone, for storing the relative paths for later.
+ * @param relativePaths Reference which stores all updated config path destinations.
+ * @param Whether we're authoritative for this config.
+ * @returns Whether a config change happened.
+ */
bool ApiListener::UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, const ConfigDirInformation& newConfigInfo,
const String& configDir, const String& zoneName, std::vector<String>& relativePaths, bool authoritative)
{
}
}
- /* Update with staging information TODO - use `authoritative` as flag. */
+ /* Log something whether we're authoritative or receing a staged config. */
Log(LogInformation, "ApiListener")
- << "Applying configuration file update for path '" << configDir << "' (" << numBytes << " Bytes). Received timestamp '"
+ << "Applying configuration file update for " << (authoritative ? "" : "stage ")
+ << "path '" << configDir << "' (" << numBytes << " Bytes). Received timestamp '"
<< Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' ("
<< std::fixed << std::setprecision(6) << newTimestamp
<< "), Current timestamp '"
<< Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", oldTimestamp) << "' ("
<< oldTimestamp << ").";
- /* TODO: Deal with recursive directories. */
+ /* If the update removes a path, delete it on disk. */
ObjectLock xlock(oldConfig);
for (const Dictionary::Pair& kv : oldConfig) {
if (!newConfig->Contains(kv.first)) {
}
}
+ /* Consider that one of the paths leaves an empty directory here. Such is not copied from stage to prod and purged then automtically. */
+
String tsPath = configDir + "/.timestamp";
if (!Utility::PathExists(tsPath)) {
std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
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)
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<Zone>()) {
}
}
+/**
+ * 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();
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;
* runtime production config and newly received configuration.
*/
String apiZonesStageDir = GetApiZonesStageDir();
- Utility::RemoveDirRecursive(apiZonesStageDir);
+
+ 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) {
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);
- Dictionary::Ptr newConfig = kv.second;
+ /* Load the current production config details. */
ConfigDirInformation currentConfigInfo = LoadConfigDir(currentConfigDir);
/* Diff the current production configuration with the received configuration.
return Empty;
}
+/**
+ * Callback for stage config validation.
+ * When validation was successful, the configuration is copied from
+ * stage to production and a restart is triggered.
+ * On failure, there's no restart and this is logged.
+ *
+ * @param pr Result of the validation process.
+ * @param stageConfigDir TODO
+ * @param currentConfigDir TODO
+ * @param relativePaths Collected paths which are copied from stage to current.
+ */
void ApiListener::TryActivateZonesStageCallback(const ProcessResult& pr,
const String& stageConfigDir, const String& currentConfigDir,
const std::vector<String>& relativePaths)
String apiZonesDir = GetApiZonesDir();
/* Purge production before copying stage. */
- Utility::RemoveDirRecursive(apiZonesDir);
+ if (Utility::PathExists(apiZonesDir))
+ Utility::RemoveDirRecursive(apiZonesDir);
+
Utility::MkDirP(apiZonesDir, 0700);
/* Copy all synced configuration files from stage to production. */
Utility::CopyFile(stagePath, currentPath);
}
- Application::RequestRestart();
- } else {
- Log(LogCritical, "ApiListener")
- << "Config validation failed for staged cluster config sync in '" << GetApiZonesStageDir()
- << "'. Aborting. Logs: '" << logFile << "'";
-
ApiListener::Ptr listener = ApiListener::GetInstance();
if (listener)
- listener->UpdateLastFailedZonesStageValidation(pr.Output);
+ listener->ClearLastFailedZonesStageValidation();
+
+ Application::RequestRestart();
+
+ return;
}
+
+ /* Error case. */
+ Log(LogCritical, "ApiListener")
+ << "Config validation failed for staged cluster config sync in '" << GetApiZonesStageDir()
+ << "'. Aborting. Logs: '" << logFile << "'";
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (listener)
+ listener->UpdateLastFailedZonesStageValidation(pr.Output);
}
+/**
+ * Spawns a new validation process and waits for its output.
+ * Sets 'System.ZonesStageVarDir' to override the config validation zone dirs with our current stage.
+ *
+ * @param stageConfigDir TODO
+ * @param currentConfigDir TODO
+ * @param relativePaths Required for later file operations in the callback.
+ */
void ApiListener::AsyncTryActivateZonesStage(const String& stageConfigDir, const String& currentConfigDir,
const std::vector<String>& relativePaths)
{
}
args->Add("--validate");
+
+ /* Set the ZonesStageDir. This creates our own local chroot without any additional automated zone includes. */
args->Add("--define");
args->Add("System.ZonesStageVarDir=" + GetApiZonesStageDir());
process->Run(std::bind(&TryActivateZonesStageCallback, _1, stageConfigDir, currentConfigDir, relativePaths));
}
+/**
+ * Update the structure from the last failed validation output.
+ * Uses the current timestamp.
+ *
+ * @param log The process output from the config validation.
+ */
void ApiListener::UpdateLastFailedZonesStageValidation(const String& log)
{
Dictionary::Ptr lastFailedZonesStageValidation = new Dictionary({
SetLastFailedZonesStageValidation(lastFailedZonesStageValidation);
}
+
+/**
+ * Clear the structure for the last failed reload.
+ *
+ */
+void ApiListener::ClearLastFailedZonesStageValidation()
+{
+ SetLastFailedZonesStageValidation(Dictionary::Ptr());
+}