From d587c962cee320c99f0d1fcdb758e27dcf290fc7 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Tue, 13 May 2014 15:57:02 +0200 Subject: [PATCH] Implement the config::Update message. Refs #6191 --- icinga-app/icinga.cpp | 3 +- lib/icinga/apievents.cpp | 30 +++---- lib/remote/apilistener-sync.cpp | 145 ++++++++++++++++++++++++++------ lib/remote/apilistener.cpp | 2 + lib/remote/apilistener.h | 6 +- lib/remote/zone.cpp | 15 +++- lib/remote/zone.h | 3 +- 7 files changed, 156 insertions(+), 48 deletions(-) diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index eed73d86f..6d8d2e0d9 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -93,7 +93,8 @@ static bool LoadConfigFiles(const String& appType) String zonesDir = Application::GetZonesDir(); - Utility::Glob(Application::GetZonesDir() + "/*", &IncludeZoneDirRecursive, GlobDirectory); + if (!zonesDir.IsEmpty()) + Utility::Glob(Application::GetZonesDir() + "/*", &IncludeZoneDirRecursive, GlobDirectory); Utility::Glob(Application::GetLocalStateDir() + "/lib/icinga2/api/zones/*", &IncludeNonLocalZone, GlobDirectory); /* Load cluster config files - this should probably be in libremote but diff --git a/lib/icinga/apievents.cpp b/lib/icinga/apievents.cpp index 278bb5311..37811a52f 100644 --- a/lib/icinga/apievents.cpp +++ b/lib/icinga/apievents.cpp @@ -129,7 +129,7 @@ Value ApiEvents::CheckResultAPIHandler(const MessageOrigin& origin, const Dictio if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->ProcessCheckResult(cr, origin); @@ -182,7 +182,7 @@ Value ApiEvents::NextCheckChangedAPIHandler(const MessageOrigin& origin, const D if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetNextCheck(params->Get("next_check"), origin); @@ -219,7 +219,7 @@ Value ApiEvents::NextNotificationChangedAPIHandler(const MessageOrigin& origin, if (!notification) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(notification)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(notification)) return Empty; notification->SetNextNotification(params->Get("next_notification"), origin); @@ -272,7 +272,7 @@ Value ApiEvents::ForceNextCheckChangedAPIHandler(const MessageOrigin& origin, co if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetForceNextCheck(params->Get("forced"), origin); @@ -325,7 +325,7 @@ Value ApiEvents::ForceNextNotificationChangedAPIHandler(const MessageOrigin& ori if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetForceNextNotification(params->Get("forced"), origin); @@ -378,7 +378,7 @@ Value ApiEvents::EnableActiveChecksChangedAPIHandler(const MessageOrigin& origin if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetEnableActiveChecks(params->Get("enabled"), origin); @@ -431,7 +431,7 @@ Value ApiEvents::EnablePassiveChecksChangedAPIHandler(const MessageOrigin& origi if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetEnablePassiveChecks(params->Get("enabled"), origin); @@ -484,7 +484,7 @@ Value ApiEvents::EnableNotificationsChangedAPIHandler(const MessageOrigin& origi if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetEnableNotifications(params->Get("enabled"), origin); @@ -537,7 +537,7 @@ Value ApiEvents::EnableFlappingChangedAPIHandler(const MessageOrigin& origin, co if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->SetEnableFlapping(params->Get("enabled"), origin); @@ -590,7 +590,7 @@ Value ApiEvents::CommentAddedAPIHandler(const MessageOrigin& origin, const Dicti if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; Comment::Ptr comment = Deserialize(params->Get("comment"), true); @@ -646,7 +646,7 @@ Value ApiEvents::CommentRemovedAPIHandler(const MessageOrigin& origin, const Dic if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->RemoveComment(params->Get("id"), origin); @@ -699,7 +699,7 @@ Value ApiEvents::DowntimeAddedAPIHandler(const MessageOrigin& origin, const Dict if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; Downtime::Ptr downtime = Deserialize(params->Get("downtime"), true); @@ -758,7 +758,7 @@ Value ApiEvents::DowntimeRemovedAPIHandler(const MessageOrigin& origin, const Di if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->RemoveDowntime(params->Get("id"), false, origin); @@ -816,7 +816,7 @@ Value ApiEvents::AcknowledgementSetAPIHandler(const MessageOrigin& origin, const if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->AcknowledgeProblem(params->Get("author"), params->Get("comment"), @@ -870,7 +870,7 @@ Value ApiEvents::AcknowledgementClearedAPIHandler(const MessageOrigin& origin, c if (!checkable) return Empty; - if (origin.FromZone && !origin.FromZone->CanAccessObject(checkable)) + if (!origin.FromZone || !origin.FromZone->CanAccessObject(checkable)) return Empty; checkable->ClearAcknowledgement(origin); diff --git a/lib/remote/apilistener-sync.cpp b/lib/remote/apilistener-sync.cpp index 556d1289f..6938d24ab 100644 --- a/lib/remote/apilistener-sync.cpp +++ b/lib/remote/apilistener-sync.cpp @@ -18,6 +18,7 @@ ******************************************************************************/ #include "remote/apilistener.h" +#include "remote/apifunction.h" #include "base/dynamictype.h" #include "base/logger_fwd.h" #include @@ -25,6 +26,8 @@ using namespace icinga; +REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler); + bool ApiListener::IsConfigMaster(const Zone::Ptr& zone) const { String path = Application::GetZonesDir() + "/" + zone->GetName(); @@ -43,33 +46,23 @@ void ApiListener::ConfigGlobHandler(const Dictionary::Ptr& config, const String& config->Set(file.SubStr(path.GetLength()), content); } -void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const +Dictionary::Ptr ApiListener::LoadConfigDir(const String& dir) { - Log(LogInformation, "remote", "Syncing zone: " + zone->GetName()); + Dictionary::Ptr config = make_shared(); + Utility::GlobRecursive(dir, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, config, dir, _1), GlobFile); + return config; +} - String dirNew = Application::GetZonesDir() + "/" + zone->GetName(); - String dirOld = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName(); +bool ApiListener::UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictionary::Ptr& newConfig, const String& configDir) +{ + bool configChange = false; -#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); + BOOST_FOREACH(const Dictionary::Pair& kv, newConfig) { + if (oldConfig->Get(kv.first) != kv.second) { + configChange = true; - 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; + String path = configDir + "/" + kv.first; Log(LogInformation, "remote", "Updating configuration file: " + path); std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::trunc); @@ -78,12 +71,40 @@ void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const } } - BOOST_FOREACH(const Dictionary::Pair& kv, configOld) { - if (!configNew->Contains(kv.first)) { - String path = dirOld + "/" + kv.first; + BOOST_FOREACH(const Dictionary::Pair& kv, oldConfig) { + if (!newConfig->Contains(kv.first)) { + configChange = true; + + String path = configDir + "/" + kv.first; (void) unlink(path.CStr()); } } + + return configChange; +} + +void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const +{ + Log(LogInformation, "remote", "Syncing zone: " + zone->GetName()); + + String newDir = Application::GetZonesDir() + "/" + zone->GetName(); + String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName(); + +#ifndef _WIN32 + if (mkdir(oldDir.CStr(), 0700) < 0 && errno != EEXIST) { +#else /*_ WIN32 */ + if (mkdir(oldDir.CStr()) < 0 && errno != EEXIST) { +#endif /* _WIN32 */ + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(oldDir)); + } + + Dictionary::Ptr newConfig = LoadConfigDir(newDir); + Dictionary::Ptr oldConfig = LoadConfigDir(oldDir); + + UpdateConfigDir(oldConfig, newConfig, oldDir); } void ApiListener::SyncZoneDirs(void) const @@ -94,13 +115,85 @@ void ApiListener::SyncZoneDirs(void) const SyncZoneDir(zone); } +} + +void ApiListener::SendConfigUpdate(const ApiClient::Ptr& aclient) +{ + Endpoint::Ptr endpoint = aclient->GetEndpoint(); + ASSERT(endpoint); + + Zone::Ptr azone = endpoint->GetZone(); + Zone::Ptr lzone = Zone::GetLocalZone(); + + /* don't try to send config updates to our master */ + if (lzone->IsChildOf(azone)) + return; + + Dictionary::Ptr configUpdate = make_shared(); + + String zonesDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones"; + + BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjects()) { + String zoneDir = zonesDir + "/" + zone->GetName(); + + if (!zone->IsChildOf(azone) || !Utility::PathExists(zoneDir)) + continue; + + configUpdate->Set(zone->GetName(), LoadConfigDir(zonesDir + "/" + zone->GetName())); + } + + Dictionary::Ptr params = make_shared(); + params->Set("update", configUpdate); + + Dictionary::Ptr message = make_shared(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "config::Update"); + message->Set("params", params); + + aclient->SendMessage(message); +} + +Value ApiListener::ConfigUpdateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params) +{ + if (!origin.FromZone || !Zone::GetLocalZone()->IsChildOf(origin.FromZone)) + return Empty; + + Dictionary::Ptr update = params->Get("update"); bool configChange = false; - // TODO: remove configuration files for zones which don't exist anymore (i.e. don't have a Zone object) + BOOST_FOREACH(const Dictionary::Pair& kv, update) { + Zone::Ptr zone = Zone::GetByName(kv.first); + + if (!zone) { + Log(LogWarning, "remote", "Ignoring config update for unknown zone: " + kv.first); + continue; + } + + String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName(); + +#ifndef _WIN32 + if (mkdir(oldDir.CStr(), 0700) < 0 && errno != EEXIST) { +#else /*_ WIN32 */ + if (mkdir(oldDir.CStr()) < 0 && errno != EEXIST) { +#endif /* _WIN32 */ + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkdir") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(oldDir)); + } + + Dictionary::Ptr newConfig = kv.second; + Dictionary::Ptr oldConfig = LoadConfigDir(oldDir); + + if (UpdateConfigDir(oldConfig, newConfig, oldDir)) + configChange = true; + } if (configChange) { Log(LogInformation, "remote", "Restarting after configuration change."); Application::RequestRestart(); } + + return Empty; } diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 9f659d82b..9135ff36c 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -223,6 +223,8 @@ void ApiListener::NewClientHandler(const Socket::Ptr& client, ConnectionRole rol ReplayLog(aclient); } + SendConfigUpdate(aclient); + endpoint->AddClient(aclient); } else AddAnonymousClient(aclient); diff --git a/lib/remote/apilistener.h b/lib/remote/apilistener.h index c7cdb7070..95638bb2e 100644 --- a/lib/remote/apilistener.h +++ b/lib/remote/apilistener.h @@ -65,6 +65,8 @@ public: void RemoveAnonymousClient(const ApiClient::Ptr& aclient); std::set GetAnonymousClients(void) const; + static Value ConfigUpdateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params); + protected: virtual void OnConfigLoaded(void); virtual void Start(void); @@ -101,11 +103,13 @@ private: static void LogGlobHandler(std::vector& files, const String& file); void ReplayLog(const ApiClient::Ptr& client); + static Dictionary::Ptr LoadConfigDir(const String& dir); + static bool UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictionary::Ptr& newConfig, const String& configDir); 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); - + void SendConfigUpdate(const ApiClient::Ptr& aclient); }; } diff --git a/lib/remote/zone.cpp b/lib/remote/zone.cpp index 7ec760751..6337744a9 100644 --- a/lib/remote/zone.cpp +++ b/lib/remote/zone.cpp @@ -39,7 +39,7 @@ std::set Zone::GetEndpoints(void) const return result; } -bool Zone::CanAccessObject(const DynamicObject::Ptr& object) const +bool Zone::CanAccessObject(const DynamicObject::Ptr& object) { Zone::Ptr object_zone; @@ -51,11 +51,18 @@ bool Zone::CanAccessObject(const DynamicObject::Ptr& object) const if (!object_zone) object_zone = Zone::GetLocalZone(); - while (object_zone) { - if (object_zone.get() == this) + return object_zone->IsChildOf(GetSelf()); +} + +bool Zone::IsChildOf(const Zone::Ptr& zone) +{ + Zone::Ptr azone = GetSelf(); + + while (azone) { + if (azone == zone) return true; - object_zone = object_zone->GetParent(); + azone = azone->GetParent(); } return false; diff --git a/lib/remote/zone.h b/lib/remote/zone.h index 1490f0fb5..b7222e0a6 100644 --- a/lib/remote/zone.h +++ b/lib/remote/zone.h @@ -39,7 +39,8 @@ public: Zone::Ptr GetParent(void) const; std::set GetEndpoints(void) const; - bool CanAccessObject(const DynamicObject::Ptr& object) const; + bool CanAccessObject(const DynamicObject::Ptr& object); + bool IsChildOf(const Zone::Ptr& zone); static Zone::Ptr GetLocalZone(void); };