]> granicus.if.org Git - icinga2/commitdiff
API: Automatically repair broken packages
authorMichael Friedrich <michael.friedrich@icinga.com>
Fri, 10 May 2019 10:48:34 +0000 (12:48 +0200)
committerMichael Friedrich <michael.friedrich@icinga.com>
Fri, 10 May 2019 10:48:34 +0000 (12:48 +0200)
This partially reverts #7150 and avoids exceptions
inside the flow. Each time an empty active stage
is detected, Icinga tries to repair it from the
the given directory tree.

Also, the code now takes into account that it should
create the package storage on startup, whether within
the API object, or if disabled, inside the application.

Caching the active stages for packages in memory
only is in effect with the API feature being enabled.
This is useful for other deployed config packages,
not only the internal one.

fixes #7173
refs #7150
refs #7119
fixes #6959

doc/15-troubleshooting.md
doc/16-upgrading-icinga-2.md
lib/cli/daemoncommand.cpp
lib/remote/apilistener.cpp
lib/remote/configobjectutility.cpp
lib/remote/configobjectutility.hpp
lib/remote/configpackageutility.cpp

index f7d46f7b8b63f0f31f41f7e04bdfa9e1ed192efc..6f4a965a96f373c40e27913844bd72c55eb329fd 100644 (file)
@@ -780,7 +780,7 @@ Wrong:
 Correct:
 
 ```
-/var/lib/icinga2/api/packages/_api/abcd-ef12-3456-7890/conf.d/downtimes/1234-5678-9012-3456.conf
+/var/lib/icinga2/api/packages/_api/dbe0bef8-c72c-4cc9-9779-da7c4527c5b2/conf.d/downtimes/1234-5678-9012-3456.conf
 ```
 
 At creation time, the object lives in memory but its storage is broken. Upon restart,
@@ -792,16 +792,17 @@ read by the Icinga daemon. This information is stored in `/var/lib/icinga2/api/p
 2.11 now limits the direct active-stage file access (this is hidden from the user),
 and caches active stages for packages in-memory.
 
-Bonus on startup/config validation: Icinga now logs a critical message when a deployed
-config package is broken.
+It also tries to repair the broken package, and lots a new message:
 
 ```
-icinga2 daemon -C
+systemctl restart icinga2
+
+tail -f /var/log/icinga2/icinga2.log
 
-[2019-04-26 12:58:14 +0200] critical/ApiListener: Cannot detect active stage for package '_api'. Broken config package, check the troubleshooting documentation.
+[2019-05-10 12:27:15 +0200] information/ConfigObjectUtility: Repairing config package '_api' with stage 'dbe0bef8-c72c-4cc9-9779-da7c4527c5b2'.
 ```
 
-In order to fix the broken config package, and mark a deployed stage as active
+If this does not happen, you can manually fixthe broken config package, and mark a deployed stage as active
 again, carefully do the following steps with creating a backup before:
 
 Navigate into the API package prefix.
@@ -820,7 +821,7 @@ ls -lahtr
 drwx------  4 michi  wheel   128B Mar 27 14:39 ..
 -rw-r--r--  1 michi  wheel    25B Mar 27 14:39 include.conf
 -rw-r--r--  1 michi  wheel   405B Mar 27 14:39 active.conf
-drwx------  7 michi  wheel   224B Mar 27 15:01 abcd-ef12-3456-7890
+drwx------  7 michi  wheel   224B Mar 27 15:01 dbe0bef8-c72c-4cc9-9779-da7c4527c5b2
 drwx------  5 michi  wheel   160B Apr 26 12:47 .
 ```
 
@@ -832,16 +833,22 @@ directory. Copy the directory name `abcd-ef12-3456-7890` and
 add it into a new file `active-stage`. This can be done like this:
 
 ```
-echo "abcd-ef12-3456-7890" > active-stage
+echo "dbe0bef8-c72c-4cc9-9779-da7c4527c5b2" > active-stage
 ```
 
-Re-run config validation.
+`active.conf` needs to have the correct active stage too, add it again
+like this. Note: This is deep down in the code, use with care!
 
 ```
-icinga2 daemon -C
+sed -i 's/ActiveStages\["_api"\].*/ActiveStages\["_api"\] = "dbe0bef8-c72c-4cc9-9779-da7c4527c5b2"/g' /var/lib/icinga2/api/packages/_api/active.conf
+```
+
+Restart Icinga 2.
+
+```
+systemctl restart icinga2
 ```
 
-The validation should not show an error.
 
 > **Note**
 >
index f3575fdd4406ad85f44d12519f4770d334e80254..5b2ba51a9b360a98a3e8726c4690771657249bf8 100644 (file)
@@ -123,12 +123,13 @@ directory path, because the active-stage file was empty/truncated/unreadable at
 this point.
 
 2.11 makes this mechanism more stable and detects broken config packages.
+It will also attempt to fix them, the following log entry is perfectly fine.
 
 ```
-[2019-04-26 12:58:14 +0200] critical/ApiListener: Cannot detect active stage for package '_api'. Broken config package, check the troubleshooting documentation.
+[2019-05-10 12:12:09 +0200] information/ConfigObjectUtility: Repairing config package '_api' with stage 'dbe0bef8-c72c-4cc9-9779-da7c4527c5b2'.
 ```
 
-In order to fix this, please follow [this troubleshooting entry](15-troubleshooting.md#troubleshooting-api-missing-runtime-objects).
+If you still encounter problems, please follow [this troubleshooting entry](15-troubleshooting.md#troubleshooting-api-missing-runtime-objects).
 
 
 ## Upgrading to v2.10 <a id="upgrading-to-2-10"></a>
index adbb1afa55b199642ff1ffcb3b20305c46a31d0e..1da4b1cc550a01d1a39a5b64bd843aa8d59132dc 100644 (file)
@@ -288,6 +288,9 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vector<std::strin
                Logger::DisableConsoleLog();
        }
 
+       /* Create the internal API object storage. Do this here too with setups without API. */
+       ConfigObjectUtility::CreateStorage();
+
        /* Remove ignored Downtime/Comment objects. */
        try {
                String configDir = ConfigObjectUtility::GetConfigDir();
index f4903e6a68557250007ed9d023de8bee18655b78..cfd207a31baf8f7f244f3d0211baf195580ec77e 100644 (file)
@@ -7,6 +7,7 @@
 #include "remote/jsonrpc.hpp"
 #include "remote/apifunction.hpp"
 #include "remote/configpackageutility.hpp"
+#include "remote/configobjectutility.hpp"
 #include "base/convert.hpp"
 #include "base/defer.hpp"
 #include "base/io-engine.hpp"
@@ -135,6 +136,9 @@ void ApiListener::OnConfigLoaded()
                Log(LogWarning, "ApiListener", "Please read the upgrading documentation for v2.8: https://icinga.com/docs/icinga2/latest/doc/16-upgrading-icinga-2/");
        }
 
+       /* Create the internal API object storage. */
+       ConfigObjectUtility::CreateStorage();
+
        /* Cache API packages and their active stage name. */
        UpdateActivePackageStagesCache();
 
index c1a0f6543e18181ed067021c7d60a190272d72c8..9aeb30c06d2bf4f49eabb38c6cbafa9d35707f26 100644 (file)
 #include "base/dependencygraph.hpp"
 #include "base/utility.hpp"
 #include <boost/algorithm/string/case_conv.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/system/error_code.hpp>
 #include <fstream>
 
 using namespace icinga;
 
 String ConfigObjectUtility::GetConfigDir()
 {
-       /* This may throw an exception the caller above must handle. */
-       return ConfigPackageUtility::GetPackageDir() + "/_api/" +
-               ConfigPackageUtility::GetActiveStage("_api");
+       String prefix = ConfigPackageUtility::GetPackageDir() + "/_api/";
+       String activeStage = ConfigPackageUtility::GetActiveStage("_api");
+
+       if (activeStage.IsEmpty())
+               RepairPackage("_api");
+
+       return prefix + activeStage;
 }
 
 String ConfigObjectUtility::GetObjectConfigPath(const Type::Ptr& type, const String& fullName)
@@ -33,6 +39,59 @@ String ConfigObjectUtility::GetObjectConfigPath(const Type::Ptr& type, const Str
                "/" + EscapeName(fullName) + ".conf";
 }
 
+void ConfigObjectUtility::RepairPackage(const String& package)
+{
+       /* Try to fix the active stage, whenever we find a directory in there.
+        * This automatically heals packages < 2.11 which remained broken.
+        */
+       namespace fs = boost::filesystem;
+
+       fs::path path(ConfigPackageUtility::GetPackageDir() + "/" + package + "/");
+
+       fs::recursive_directory_iterator end;
+
+       String foundActiveStage;
+
+       for (fs::recursive_directory_iterator it(path); it != end; it++) {
+               boost::system::error_code ec;
+
+               const fs::path d = *it;
+               if (fs::is_directory(d, ec)) {
+                       /* Extract the relative directory name. */
+                       foundActiveStage = d.stem().string();
+
+                       break; // Use the first found directory.
+               }
+       }
+
+       if (!foundActiveStage.IsEmpty()) {
+               Log(LogInformation, "ConfigObjectUtility")
+                       << "Repairing config package '" << package << "' with stage '" << foundActiveStage << "'.";
+
+               ConfigPackageUtility::ActivateStage(package, foundActiveStage);
+       } else {
+               BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot repair package '" + package + "', please check the troubleshooting docs."));
+       }
+}
+
+void ConfigObjectUtility::CreateStorage()
+{
+       boost::mutex::scoped_lock lock(ConfigPackageUtility::GetStaticPackageMutex());
+
+       /* For now, we only use _api as our creation target. */
+       String package = "_api";
+
+       if (!ConfigPackageUtility::PackageExists(package)) {
+               Log(LogNotice, "ConfigObjectUtility")
+                       << "Package " << package << " doesn't exist yet, creating it.";
+
+               ConfigPackageUtility::CreatePackage(package);
+
+               String stage = ConfigPackageUtility::CreateStage(package);
+               ConfigPackageUtility::ActivateStage(package, stage);
+       }
+}
+
 String ConfigObjectUtility::EscapeName(const String& name)
 {
        return Utility::EscapeString(name, "<>:\"/\\|?*", true);
@@ -88,16 +147,7 @@ String ConfigObjectUtility::CreateObjectConfig(const Type::Ptr& type, const Stri
 bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& fullName,
        const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation)
 {
-       {
-               boost::mutex::scoped_lock lock(ConfigPackageUtility::GetStaticPackageMutex());
-
-               if (!ConfigPackageUtility::PackageExists("_api")) {
-                       ConfigPackageUtility::CreatePackage("_api");
-
-                       String stage = ConfigPackageUtility::CreateStage("_api");
-                       ConfigPackageUtility::ActivateStage("_api", stage);
-               }
-       }
+       CreateStorage();
 
        ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(type, fullName);
 
index 5de16d2c93f432aaabd5ffcf7dc4037231a61a15..404bc3bad567861096e63345f1bbb561975b2ee6 100644 (file)
@@ -23,6 +23,8 @@ class ConfigObjectUtility
 public:
        static String GetConfigDir();
        static String GetObjectConfigPath(const Type::Ptr& type, const String& fullName);
+       static void RepairPackage(const String& package);
+       static void CreateStorage();
 
        static String CreateObjectConfig(const Type::Ptr& type, const String& fullName,
                bool ignoreOnError, const Array::Ptr& templates, const Dictionary::Ptr& attrs);
index d0bf90061f2acc7defea886f244ee90fd02afff2..ac877d16cbbf3e96cc4df78240a5ce81b4e0ddd3 100644 (file)
@@ -265,7 +265,7 @@ String ConfigPackageUtility::GetActiveStageFromFile(const String& packageName)
        fp.close();
 
        if (fp.fail())
-               BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot detect active stage for package '" + packageName + "'. Broken config package, check the troubleshooting documentation."));
+               return ""; /* Don't use exceptions here. The caller must deal with empty stages at this point. Happens on initial package creation for example. */
 
        return stage.Trim();
 }
@@ -283,13 +283,16 @@ void ConfigPackageUtility::SetActiveStageToFile(const String& packageName, const
 
 String ConfigPackageUtility::GetActiveStage(const String& packageName)
 {
+       String activeStage;
+
        ApiListener::Ptr listener = ApiListener::GetInstance();
 
-       /* config packages without API make no sense. */
+       /* If we don't have an API feature, just use the file storage without caching this.
+        * This happens when ScheduledDowntime objects generate Downtime objects.
+        * TODO: Make the API a first class citizen.
+        */
        if (!listener)
-               BOOST_THROW_EXCEPTION(std::invalid_argument("No ApiListener instance configured."));
-
-       String activeStage;
+               return GetActiveStageFromFile(packageName);
 
        /* First use runtime state. */
        try {
@@ -301,8 +304,6 @@ String ConfigPackageUtility::GetActiveStage(const String& packageName)
                /* When we've read something, correct memory. */
                if (!activeStage.IsEmpty())
                        listener->SetActivePackageStage(packageName, activeStage);
-               else
-                       BOOST_THROW_EXCEPTION(std::invalid_argument("Cannot detect active stage for package '" + packageName + "'. Broken config package, check the troubleshooting documentation."));
        }
 
        return activeStage;
@@ -310,16 +311,16 @@ String ConfigPackageUtility::GetActiveStage(const String& packageName)
 
 void ConfigPackageUtility::SetActiveStage(const String& packageName, const String& stageName)
 {
+       /* Update the marker on disk for restarts. */
+       SetActiveStageToFile(packageName, stageName);
+
        ApiListener::Ptr listener = ApiListener::GetInstance();
 
-       /* config packages without API make no sense. */
+       /* No API, no caching. */
        if (!listener)
-               BOOST_THROW_EXCEPTION(std::invalid_argument("No ApiListener instance configured."));
+               return;
 
        listener->SetActivePackageStage(packageName, stageName);
-
-       /* Also update the marker on disk for restarts. */
-       SetActiveStageToFile(packageName, stageName);
 }
 
 std::vector<std::pair<String, bool> > ConfigPackageUtility::GetFiles(const String& packageName, const String& stageName)