Implement the config::Update message.

Refs #6191
This commit is contained in:
Gunnar Beutner 2014-05-13 15:57:02 +02:00
parent 3a1ce23aeb
commit d587c962ce
7 changed files with 156 additions and 48 deletions

View File

@ -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

View File

@ -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);

View File

@ -18,6 +18,7 @@
******************************************************************************/
#include "remote/apilistener.h"
#include "remote/apifunction.h"
#include "base/dynamictype.h"
#include "base/logger_fwd.h"
#include <boost/foreach.hpp>
@ -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<Dictionary>();
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<Dictionary>();
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<Dictionary>();
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<Dictionary>();
String zonesDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjects<Zone>()) {
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<Dictionary>();
params->Set("update", configUpdate);
Dictionary::Ptr message = make_shared<Dictionary>();
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;
}

View File

@ -223,6 +223,8 @@ void ApiListener::NewClientHandler(const Socket::Ptr& client, ConnectionRole rol
ReplayLog(aclient);
}
SendConfigUpdate(aclient);
endpoint->AddClient(aclient);
} else
AddAnonymousClient(aclient);

View File

@ -65,6 +65,8 @@ public:
void RemoveAnonymousClient(const ApiClient::Ptr& aclient);
std::set<ApiClient::Ptr> 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<int>& 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);
};
}

View File

@ -39,7 +39,7 @@ std::set<Endpoint::Ptr> 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;

View File

@ -39,7 +39,8 @@ public:
Zone::Ptr GetParent(void) const;
std::set<Endpoint::Ptr> 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);
};