From 0d36cc8d5fbfe1536cfef1b086332508c0f4d054 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Tue, 13 May 2014 13:18:27 +0200 Subject: [PATCH] Implement support for the zones.d config directory. Refs #6191 --- doc/5-configuring-icinga-2.md | 1 + etc/CMakeLists.txt | 1 + icinga-app/icinga.cpp | 28 ++++++++ lib/base/application.cpp | 20 ++++++ lib/base/application.h | 3 + lib/remote/CMakeLists.txt | 8 ++- lib/remote/apilistener-sync.cpp | 113 ++++++++++++++++++++++++++++++++ lib/remote/apilistener.cpp | 2 + lib/remote/apilistener.h | 6 ++ lib/remote/apilistener.ti | 1 + lib/remote/remote-type.conf | 2 +- 11 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 lib/remote/apilistener-sync.cpp diff --git a/doc/5-configuring-icinga-2.md b/doc/5-configuring-icinga-2.md index 35218ae92..8f277437a 100644 --- a/doc/5-configuring-icinga-2.md +++ b/doc/5-configuring-icinga-2.md @@ -516,6 +516,7 @@ Variable |Description --------------------|------------------- PrefixDir |**Read-only.** Contains the installation prefix that was specified with cmake -DCMAKE_INSTALL_PREFIX. Defaults to "/usr/local". SysconfDir |**Read-only.** Contains the path of the sysconf directory. Defaults to PrefixDir + "/etc". +ZonesDir |**Read-only.** Contains the path of the zones.d directory. Defaults to SysconfDir + "/zones.d". LocalStateDir |**Read-only.** Contains the path of the local state directory. Defaults to PrefixDir + "/var". PkgDataDir |**Read-only.** Contains the path of the package data directory. Defaults to PrefixDir + "/share/icinga2". StatePath |**Read-write.** Contains the path of the Icinga 2 state file. Defaults to LocalStateDir + "/lib/icinga2/icinga2.state". diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt index 154fab4ae..133d1cd3a 100644 --- a/etc/CMakeLists.txt +++ b/etc/CMakeLists.txt @@ -56,6 +56,7 @@ install_if_not_exists(icinga2/features-available/syslog.conf ${CMAKE_INSTALL_SYS install_if_not_exists(icinga2/scripts/check_kernel ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/scripts) install_if_not_exists(icinga2/scripts/mail-host-notification.sh ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/scripts) install_if_not_exists(icinga2/scripts/mail-service-notification.sh ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/scripts) +install_if_not_exists(icinga2/zones.d/README ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/zones.d) install_if_not_exists(logrotate.d/icinga2 ${CMAKE_INSTALL_SYSCONFDIR}/logrotate.d) install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_SYSCONFDIR}/icinga2/pki\")") diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 7084355ba..88e548129 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -65,6 +65,27 @@ static String LoadAppType(const String& typeSpec) return typeSpec.SubStr(index + 1); } +static void IncludeDirRecursive(const String& path) +{ + Utility::GlobRecursive(path, "*.conf", &ConfigCompiler::CompileFile, GlobFile); +} + +static void IncludeNonLocalZone(const String& zonePath) +{ + String etcPath = Application::GetZonesDir() + "/" + Utility::BaseName(zonePath); + +#ifndef _WIN32 + struct stat statbuf; + if (lstat(etcPath.CStr(), &statbuf) >= 0) +#else /* _WIN32 */ + struct _stat statbuf; + if (_stat(etcPath.CStr(), &statbuf) >= 0) +#endif /* _WIN32 */ + return; + + IncludeDirRecursive(zonePath); +} + static bool LoadConfigFiles(const String& appType) { ConfigCompilerContext::GetInstance()->Reset(); @@ -75,6 +96,12 @@ static bool LoadConfigFiles(const String& appType) } } + IncludeDirRecursive(Application::GetZonesDir()); + Utility::Glob(Application::GetLocalStateDir() + "/lib/icinga2/api/zones/*", &IncludeNonLocalZone, GlobDirectory); + + /* Load cluster config files - this should probably be in libremote but + * unfortunately moving it there is somewhat non-trivial. */ + String name, fragment; BOOST_FOREACH(boost::tie(name, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) { ConfigCompiler::CompileText(name, fragment); @@ -265,6 +292,7 @@ int Main(void) } #endif /* _WIN32 */ + Application::DeclareZonesDir(Application::GetSysconfDir() + "/icinga2/zones.d"); Application::DeclareApplicationType("icinga/IcingaApplication"); po::options_description desc("Supported options"); diff --git a/lib/base/application.cpp b/lib/base/application.cpp index 754a9f77a..4b8672958 100644 --- a/lib/base/application.cpp +++ b/lib/base/application.cpp @@ -790,6 +790,26 @@ String Application::GetLocalStateDir(void) return ScriptVariable::Get("LocalStateDir"); } +/** + * Sets the path of the zones dir. + * + * @param path The new path. + */ +void Application::DeclareZonesDir(const String& path) +{ + ScriptVariable::Set("ZonesDir", path, false); +} + +/** + * Retrieves the path for the local state dir. + * + * @returns The path. + */ +String Application::GetZonesDir(void) +{ + return ScriptVariable::Get("ZonesDir"); +} + /** * Sets the path for the local state dir. * diff --git a/lib/base/application.h b/lib/base/application.h index 4b053c10f..1c15f3cea 100644 --- a/lib/base/application.h +++ b/lib/base/application.h @@ -79,6 +79,9 @@ public: static String GetSysconfDir(void); static void DeclareSysconfDir(const String& path); + static String GetZonesDir(void); + static void DeclareZonesDir(const String& path); + static String GetLocalStateDir(void); static void DeclareLocalStateDir(const String& path); diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index ed9fdc9fc..f2c1ff0c3 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -22,9 +22,9 @@ mkclass_target(zone.ti zone.th) mkembedconfig_target(remote-type.conf remote-type.cpp) add_library(remote SHARED - apiclient.cpp apifunction.cpp apilistener.cpp apilistener.th authority.cpp - endpoint.cpp endpoint.th jsonrpc.cpp messageorigin.cpp remote-type.cpp - zone.cpp zone.th + apiclient.cpp apifunction.cpp apilistener.cpp apilistener-sync.cpp + apilistener.th authority.cpp endpoint.cpp endpoint.th jsonrpc.cpp + messageorigin.cpp remote-type.cpp zone.cpp zone.th ) include_directories(${Boost_INCLUDE_DIRS}) @@ -46,4 +46,6 @@ install( #install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api\")") install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/log\")") install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/repository\")") +install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/zones\")") + diff --git a/lib/remote/apilistener-sync.cpp b/lib/remote/apilistener-sync.cpp new file mode 100644 index 000000000..6bfc9e57b --- /dev/null +++ b/lib/remote/apilistener-sync.cpp @@ -0,0 +1,113 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "remote/apilistener.h" +#include "base/dynamictype.h" +#include "base/logger_fwd.h" +#include +#include + +using namespace icinga; + +bool ApiListener::IsConfigMaster(const Zone::Ptr& zone) const +{ + String path = Application::GetZonesDir() + "/" + zone->GetName(); + +#ifndef _WIN32 + struct stat statbuf; + return (lstat(path.CStr(), &statbuf) >= 0); +#else /* _WIN32 */ + struct _stat statbuf; + return (_stat(path.CStr(), &statbuf) >= 0); +#endif /* _WIN32 */ +} + +void ApiListener::ConfigGlobHandler(const Dictionary::Ptr& config, const String& path, const String& file) +{ + CONTEXT("Creating config update for file '" + file + "'"); + + std::ifstream fp(file.CStr()); + if (!fp) + return; + + String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); + config->Set(file.SubStr(path.GetLength()), content); +} + +void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const +{ + Log(LogInformation, "remote", "Syncing zone: " + zone->GetName()); + + String dirNew = Application::GetZonesDir() + "/" + zone->GetName(); + String dirOld = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName(); + +#ifndef _WIN32 + if (mkdir(dirOld.CStr(), 0700) < 0 && errno != EEXIST) { +#else /*_ WIN32 */ + if (mkdir(dirOld.CStr()) < 0 && errno != EEXIST) { +#endif /* _WIN32 */ + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(dirOld)); + } + + Dictionary::Ptr configNew = make_shared(); + Utility::GlobRecursive(dirNew, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, configNew, dirNew, _1), GlobFile); + + Dictionary::Ptr configOld = make_shared(); + Utility::GlobRecursive(dirOld, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, configOld, dirOld, _1), GlobFile); + + BOOST_FOREACH(const Dictionary::Pair& kv, configNew) { + if (configOld->Get(kv.first) != kv.second) { + String path = dirOld + "/" + kv.first; + Log(LogInformation, "remote", "Updating configuration file: " + path); + + std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::trunc); + fp << kv.second; + fp.close(); + } + } + + BOOST_FOREACH(const Dictionary::Pair& kv, configOld) { + if (!configNew->Contains(kv.first)) { + String path = dirOld + "/" + kv.first; + (void) unlink(path.CStr()); + } + } +} + +void ApiListener::SyncZoneDirs(void) const +{ + BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjects()) { + if (!IsConfigMaster(zone)) + continue; + + SyncZoneDir(zone); + } + + bool configChange = false; + + // TODO: remove configuration files for zones which don't exist anymore (i.e. don't have a Zone object) + + if (configChange) { + Log(LogInformation, "remote", "Restarting after configuration change."); + Application::RequestRestart(); + } +} diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 095d94112..9f659d82b 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -53,6 +53,8 @@ void ApiListener::OnConfigLoaded(void) if (!Endpoint::GetByName(GetIdentity())) BOOST_THROW_EXCEPTION(std::runtime_error("Endpoint object for '" + GetIdentity() + "' is missing.")); + + SyncZoneDirs(); } /** diff --git a/lib/remote/apilistener.h b/lib/remote/apilistener.h index cf133a335..c7cdb7070 100644 --- a/lib/remote/apilistener.h +++ b/lib/remote/apilistener.h @@ -100,6 +100,12 @@ private: void CloseLogFile(void); static void LogGlobHandler(std::vector& files, const String& file); void ReplayLog(const ApiClient::Ptr& client); + + void SyncZoneDirs(void) const; + void SyncZoneDir(const Zone::Ptr& zone) const; + bool IsConfigMaster(const Zone::Ptr& zone) const; + static void ConfigGlobHandler(const Dictionary::Ptr& config, const String& path, const String& file); + }; } diff --git a/lib/remote/apilistener.ti b/lib/remote/apilistener.ti index d19d4b992..381536c33 100644 --- a/lib/remote/apilistener.ti +++ b/lib/remote/apilistener.ti @@ -1,4 +1,5 @@ #include "base/dynamicobject.h" +#include "base/application.h" namespace icinga { diff --git a/lib/remote/remote-type.conf b/lib/remote/remote-type.conf index 3f8da6042..bdf58db5f 100644 --- a/lib/remote/remote-type.conf +++ b/lib/remote/remote-type.conf @@ -30,7 +30,7 @@ %attribute %string "crl_path", %attribute %string "bind_host", - %attribute %string "bind_port" + %attribute %string "bind_port", } %type Endpoint { -- 2.40.0