+
+/**
+ * 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 logFile = GetApiZonesStageDir() + "/startup.log";
+ std::ofstream fpLog(logFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpLog << pr.Output;
+ fpLog.close();
+
+ String statusFile = GetApiZonesStageDir() + "/status";
+ std::ofstream fpStatus(statusFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
+ fpStatus << pr.ExitStatus;
+ fpStatus.close();
+
+ /* validation went fine, copy stage and reload */
+ if (pr.ExitStatus == 0) {
+ Log(LogInformation, "ApiListener")
+ << "Config validation for stage '" << GetApiZonesStageDir() << "' was OK, replacing into '" << GetApiZonesDir() << "' and triggering reload.";
+
+ String apiZonesDir = GetApiZonesDir();
+
+ /* Purge production before copying stage. */
+ if (Utility::PathExists(apiZonesDir))
+ Utility::RemoveDirRecursive(apiZonesDir);
+
+ Utility::MkDirP(apiZonesDir, 0700);
+
+ /* Copy all synced configuration files from stage to production. */
+ for (const String& path : relativePaths) {
+ Log(LogNotice, "ApiListener")
+ << "Copying file '" << path << "' from config sync staging to production zones directory.";
+
+ String stagePath = GetApiZonesStageDir() + path;
+ String currentPath = GetApiZonesDir() + path;
+
+ Utility::MkDirP(Utility::DirName(currentPath), 0700);
+
+ Utility::CopyFile(stagePath, currentPath);
+ }
+
+ ApiListener::Ptr listener = ApiListener::GetInstance();
+
+ if (listener)
+ 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)
+{
+ VERIFY(Application::GetArgC() >= 1);
+
+ /* Inherit parent process args. */
+ Array::Ptr args = new Array({
+ Application::GetExePath(Application::GetArgV()[0]),
+ });
+
+ for (int i = 1; i < Application::GetArgC(); i++) {
+ String argV = Application::GetArgV()[i];
+
+ if (argV == "-d" || argV == "--daemonize")
+ continue;
+
+ args->Add(argV);
+ }
+
+ 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::Ptr process = new Process(Process::PrepareCommand(args));
+ process->SetTimeout(300);
+ 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({
+ { "log", log },
+ { "ts", Utility::GetTime() }
+ });
+
+ SetLastFailedZonesStageValidation(lastFailedZonesStageValidation);
+}
+
+/**
+ * Clear the structure for the last failed reload.
+ *
+ */
+void ApiListener::ClearLastFailedZonesStageValidation()
+{
+ SetLastFailedZonesStageValidation(Dictionary::Ptr());
+}