2014-05-13 13:18:27 +02:00
|
|
|
/******************************************************************************
|
|
|
|
* 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. *
|
|
|
|
******************************************************************************/
|
|
|
|
|
2014-05-25 16:23:35 +02:00
|
|
|
#include "remote/apilistener.hpp"
|
|
|
|
#include "remote/apifunction.hpp"
|
|
|
|
#include "base/dynamictype.hpp"
|
|
|
|
#include "base/logger_fwd.hpp"
|
|
|
|
#include "base/convert.hpp"
|
2014-05-13 13:18:27 +02:00
|
|
|
#include <boost/foreach.hpp>
|
|
|
|
#include <fstream>
|
|
|
|
|
|
|
|
using namespace icinga;
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler);
|
|
|
|
|
2014-05-13 13:18:27 +02:00
|
|
|
bool ApiListener::IsConfigMaster(const Zone::Ptr& zone) const
|
|
|
|
{
|
|
|
|
String path = Application::GetZonesDir() + "/" + zone->GetName();
|
2014-05-13 14:40:12 +02:00
|
|
|
return Utility::PathExists(path);
|
2014-05-13 13:18:27 +02:00
|
|
|
}
|
|
|
|
|
2014-06-12 22:51:48 +02:00
|
|
|
void ApiListener::ConfigGlobHandler(Dictionary::Ptr& config, const String& path, const String& file)
|
2014-05-13 13:18:27 +02:00
|
|
|
{
|
|
|
|
CONTEXT("Creating config update for file '" + file + "'");
|
|
|
|
|
2014-06-12 22:51:48 +02:00
|
|
|
Log(LogNotice, "ApiListener", "Creating config update for file '" + file + "'");
|
|
|
|
|
2014-05-13 13:18:27 +02:00
|
|
|
std::ifstream fp(file.CStr());
|
|
|
|
if (!fp)
|
|
|
|
return;
|
|
|
|
|
|
|
|
String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
|
|
|
|
config->Set(file.SubStr(path.GetLength()), content);
|
|
|
|
}
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
Dictionary::Ptr ApiListener::LoadConfigDir(const String& dir)
|
2014-05-13 13:18:27 +02:00
|
|
|
{
|
2014-05-13 15:57:02 +02:00
|
|
|
Dictionary::Ptr config = make_shared<Dictionary>();
|
2014-06-12 22:51:48 +02:00
|
|
|
Utility::GlobRecursive(dir, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, boost::ref(config), dir, _1), GlobFile);
|
2014-05-13 15:57:02 +02:00
|
|
|
return config;
|
|
|
|
}
|
2014-05-13 13:18:27 +02:00
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
bool ApiListener::UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictionary::Ptr& newConfig, const String& configDir)
|
|
|
|
{
|
|
|
|
bool configChange = false;
|
2014-05-13 13:18:27 +02:00
|
|
|
|
2014-05-15 14:27:50 +02:00
|
|
|
if (oldConfig->Contains(".timestamp") && newConfig->Contains(".timestamp")) {
|
|
|
|
double oldTS = Convert::ToDouble(oldConfig->Get(".timestamp"));
|
|
|
|
double newTS = Convert::ToDouble(newConfig->Get(".timestamp"));
|
|
|
|
|
|
|
|
/* skip update if our config is newer */
|
|
|
|
if (oldTS <= newTS)
|
|
|
|
return false;
|
|
|
|
}
|
2014-05-13 13:18:27 +02:00
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
BOOST_FOREACH(const Dictionary::Pair& kv, newConfig) {
|
|
|
|
if (oldConfig->Get(kv.first) != kv.second) {
|
|
|
|
configChange = true;
|
2014-05-13 13:18:27 +02:00
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
String path = configDir + "/" + kv.first;
|
2014-05-28 13:58:56 +02:00
|
|
|
Log(LogInformation, "ApiListener", "Updating configuration file: " + path);
|
2014-05-13 13:18:27 +02:00
|
|
|
|
2014-06-13 09:10:35 +02:00
|
|
|
//pass the directory and generate a dir tree, if not existing already
|
|
|
|
Utility::MkDirP(Utility::DirName(path), 0755);
|
2014-05-13 13:18:27 +02:00
|
|
|
std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::trunc);
|
|
|
|
fp << kv.second;
|
|
|
|
fp.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
BOOST_FOREACH(const Dictionary::Pair& kv, oldConfig) {
|
|
|
|
if (!newConfig->Contains(kv.first)) {
|
|
|
|
configChange = true;
|
|
|
|
|
|
|
|
String path = configDir + "/" + kv.first;
|
2014-05-13 13:18:27 +02:00
|
|
|
(void) unlink(path.CStr());
|
|
|
|
}
|
|
|
|
}
|
2014-05-13 15:57:02 +02:00
|
|
|
|
2014-05-15 14:27:50 +02:00
|
|
|
String tsPath = configDir + "/.timestamp";
|
|
|
|
if (!Utility::PathExists(tsPath)) {
|
|
|
|
std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
|
|
|
|
fp << Utility::GetTime();
|
|
|
|
fp.close();
|
|
|
|
}
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
return configChange;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
|
|
|
|
{
|
2014-05-28 13:58:56 +02:00
|
|
|
Log(LogInformation, "ApiListener", "Syncing zone: " + zone->GetName());
|
2014-05-13 15:57:02 +02:00
|
|
|
|
|
|
|
String newDir = Application::GetZonesDir() + "/" + zone->GetName();
|
|
|
|
String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
|
|
|
|
|
2014-06-13 09:23:05 +02:00
|
|
|
if (!Utility::MkDir(oldDir, 0700)) {
|
|
|
|
std::ostringstream msgbuf;
|
|
|
|
msgbuf << "mkdir() for path '" << oldDir << "'failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
|
|
|
|
Log(LogCritical, "ApiListener", msgbuf.str());
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
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);
|
2014-05-13 13:18:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void ApiListener::SyncZoneDirs(void) const
|
|
|
|
{
|
|
|
|
BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjects<Zone>()) {
|
|
|
|
if (!IsConfigMaster(zone))
|
|
|
|
continue;
|
|
|
|
|
2014-06-13 09:23:05 +02:00
|
|
|
try {
|
|
|
|
SyncZoneDir(zone);
|
|
|
|
} catch (std::exception&) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-05-13 13:18:27 +02:00
|
|
|
}
|
2014-05-13 15:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 */
|
2014-05-23 18:35:43 +02:00
|
|
|
if (!azone->IsChildOf(lzone))
|
2014-05-13 15:57:02 +02:00
|
|
|
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();
|
|
|
|
|
2014-06-12 14:31:07 +02:00
|
|
|
if (!zone->IsChildOf(azone) && !zone->IsGlobal()) {
|
|
|
|
Log(LogNotice, "ApiListener", "Skipping sync for '" + zone->GetName() + "'. Not a child of zone '" + azone->GetName() + "'.");
|
2014-05-13 15:57:02 +02:00
|
|
|
continue;
|
2014-06-12 14:31:07 +02:00
|
|
|
}
|
|
|
|
if (!Utility::PathExists(zoneDir)) {
|
|
|
|
Log(LogNotice, "ApiListener", "Ignoring sync for '" + zone->GetName() + "'. Zone directory '" + zoneDir + "' does not exist.");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (zone->IsGlobal())
|
|
|
|
Log(LogInformation, "ApiListener", "Syncing global zone '" + zone->GetName() + "'.");
|
2014-05-13 15:57:02 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2014-05-23 18:35:43 +02:00
|
|
|
if (!origin.FromClient->GetEndpoint() || (origin.FromZone && !Zone::GetLocalZone()->IsChildOf(origin.FromZone)))
|
2014-05-13 15:57:02 +02:00
|
|
|
return Empty;
|
|
|
|
|
2014-05-15 10:13:32 +02:00
|
|
|
ApiListener::Ptr listener = ApiListener::GetInstance();
|
|
|
|
|
2014-06-12 14:31:07 +02:00
|
|
|
if (!listener) {
|
|
|
|
Log(LogCritical, "ApiListener", "No instance available.");
|
2014-05-15 10:13:32 +02:00
|
|
|
return Empty;
|
2014-06-12 14:31:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!listener->GetAcceptConfig()) {
|
|
|
|
Log(LogWarning, "ApiListener", "Ignoring config update. '" + listener->GetName() + "' does not accept config.");
|
|
|
|
return Empty;
|
|
|
|
}
|
2014-05-15 10:13:32 +02:00
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
Dictionary::Ptr update = params->Get("update");
|
2014-05-13 13:18:27 +02:00
|
|
|
|
|
|
|
bool configChange = false;
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
BOOST_FOREACH(const Dictionary::Pair& kv, update) {
|
|
|
|
Zone::Ptr zone = Zone::GetByName(kv.first);
|
|
|
|
|
|
|
|
if (!zone) {
|
2014-05-28 13:58:56 +02:00
|
|
|
Log(LogWarning, "ApiListener", "Ignoring config update for unknown zone: " + kv.first);
|
2014-05-13 15:57:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
|
|
|
|
|
2014-06-13 09:23:05 +02:00
|
|
|
if (!Utility::MkDir(oldDir, 0700)) {
|
|
|
|
std::ostringstream msgbuf;
|
|
|
|
msgbuf << "mkdir() for path '" << oldDir << "'failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
|
|
|
|
Log(LogCritical, "ApiListener", msgbuf.str());
|
|
|
|
|
2014-05-13 15:57:02 +02:00
|
|
|
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;
|
|
|
|
}
|
2014-05-13 13:18:27 +02:00
|
|
|
|
|
|
|
if (configChange) {
|
2014-05-28 13:58:56 +02:00
|
|
|
Log(LogInformation, "ApiListener", "Restarting after configuration change.");
|
2014-05-13 13:18:27 +02:00
|
|
|
Application::RequestRestart();
|
|
|
|
}
|
2014-05-13 15:57:02 +02:00
|
|
|
|
|
|
|
return Empty;
|
2014-05-13 13:18:27 +02:00
|
|
|
}
|