]> granicus.if.org Git - icinga2/blobdiff - lib/remote/apilistener-filesync.cpp
Leave partial deletes as is, this is dealt with stage purge later
[icinga2] / lib / remote / apilistener-filesync.cpp
index ffddb51f5d0b4abf4c9ee6c034af3c292fa53479..4b31673f699d44527014fa292d12efda50dfd98e 100644 (file)
@@ -17,6 +17,14 @@ 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().
+ *
+ * @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 + "'");
@@ -32,6 +40,11 @@ void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String&
 
        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
@@ -40,6 +53,12 @@ void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String&
        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();
@@ -53,6 +72,12 @@ Dictionary::Ptr ApiListener::MergeConfigUpdate(const ConfigDirInformation& confi
        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;
@@ -62,6 +87,23 @@ ConfigDirInformation ApiListener::LoadConfigDir(const String& dir)
        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)
 {
@@ -127,16 +169,17 @@ bool ApiListener::UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, con
                }
        }
 
-       /* 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)) {
@@ -147,6 +190,8 @@ bool ApiListener::UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, con
                }
        }
 
+       /* 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);
@@ -165,6 +210,17 @@ 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)
@@ -214,6 +270,10 @@ void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
        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>()) {
@@ -225,6 +285,14 @@ void ApiListener::SyncZoneDirs() const
        }
 }
 
+/**
+ * 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();
@@ -272,8 +340,21 @@ void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
        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;
 
@@ -310,9 +391,13 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
         * 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) {
 
@@ -340,13 +425,14 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
                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.
@@ -366,6 +452,17 @@ Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const D
        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)
@@ -388,7 +485,9 @@ void ApiListener::TryActivateZonesStageCallback(const ProcessResult& pr,
                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. */
@@ -404,19 +503,35 @@ void ApiListener::TryActivateZonesStageCallback(const ProcessResult& pr,
                        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)
 {
@@ -437,6 +552,8 @@ void ApiListener::AsyncTryActivateZonesStage(const String& stageConfigDir, const
        }
 
        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());
 
@@ -445,6 +562,12 @@ void ApiListener::AsyncTryActivateZonesStage(const String& stageConfigDir, const
        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({
@@ -454,3 +577,12 @@ void ApiListener::UpdateLastFailedZonesStageValidation(const String& log)
 
        SetLastFailedZonesStageValidation(lastFailedZonesStageValidation);
 }
+
+/**
+ * Clear the structure for the last failed reload.
+ *
+ */
+void ApiListener::ClearLastFailedZonesStageValidation()
+{
+       SetLastFailedZonesStageValidation(Dictionary::Ptr());
+}