/****************************************************************************** * Icinga 2 * * Copyright (C) 2012-2016 Icinga Development Team (https://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.hpp" #include "remote/apifunction.hpp" #include "config/configcompiler.hpp" #include "base/configtype.hpp" #include "base/logger.hpp" #include "base/convert.hpp" #include "base/exception.hpp" #include #include #include using namespace icinga; REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler); void ApiListener::ConfigGlobHandler(Dictionary::Ptr& config, const String& path, const String& file) { CONTEXT("Creating config update for file '" + file + "'"); Log(LogNotice, "ApiListener") << "Creating config update for file '" << file << "'"; std::ifstream fp(file.CStr(), std::ifstream::binary); if (!fp) return; String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); config->Set(file.SubStr(path.GetLength()), content); } Dictionary::Ptr ApiListener::LoadConfigDir(const String& dir) { Dictionary::Ptr config = new Dictionary(); Utility::GlobRecursive(dir, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, boost::ref(config), dir, _1), GlobFile); return config; } bool ApiListener::UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictionary::Ptr& newConfig, const String& configDir, bool authoritative) { bool configChange = false; double oldTimestamp; if (!oldConfig->Contains(".timestamp")) oldTimestamp = 0; else oldTimestamp = oldConfig->Get(".timestamp"); double newTimestamp; if (!newConfig->Contains(".timestamp")) newTimestamp = Utility::GetTime(); else newTimestamp = newConfig->Get(".timestamp"); /* skip update if our config is newer */ if (oldTimestamp >= newTimestamp) return false; { ObjectLock olock(newConfig); BOOST_FOREACH(const Dictionary::Pair& kv, newConfig) { if (oldConfig->Get(kv.first) != kv.second) { configChange = true; String path = configDir + "/" + kv.first; Log(LogInformation, "ApiListener") << "Updating configuration file: " << path; //pass the directory and generate a dir tree, if not existing already Utility::MkDirP(Utility::DirName(path), 0755); std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); fp << kv.second; fp.close(); } } } ObjectLock xlock(oldConfig); BOOST_FOREACH(const Dictionary::Pair& kv, oldConfig) { if (!newConfig->Contains(kv.first)) { configChange = true; String path = configDir + "/" + kv.first; (void) unlink(path.CStr()); } } String tsPath = configDir + "/.timestamp"; if (!Utility::PathExists(tsPath)) { std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc); fp << std::fixed << newTimestamp; fp.close(); } if (authoritative) { String authPath = configDir + "/.authoritative"; if (!Utility::PathExists(authPath)) { std::ofstream fp(authPath.CStr(), std::ofstream::out | std::ostream::trunc); fp.close(); } } return configChange; } void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const { Dictionary::Ptr newConfig = new Dictionary(); BOOST_FOREACH(const ZoneFragment& zf, ConfigCompiler::GetZoneDirs(zone->GetName())) { Dictionary::Ptr newConfigPart = LoadConfigDir(zf.Path); ObjectLock olock(newConfigPart); BOOST_FOREACH(const Dictionary::Pair& kv, newConfigPart) { newConfig->Set("/" + zf.Tag + kv.first, kv.second); } } if (newConfig->GetLength() == 0) return; String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName(); Log(LogInformation, "ApiListener") << "Copying " << newConfig->GetLength() << " zone configuration files for zone '" << zone->GetName() << "' to '" << oldDir << "'."; Utility::MkDirP(oldDir, 0700); Dictionary::Ptr oldConfig = LoadConfigDir(oldDir); UpdateConfigDir(oldConfig, newConfig, oldDir, true); } void ApiListener::SyncZoneDirs(void) const { BOOST_FOREACH(const Zone::Ptr& zone, ConfigType::GetObjectsByType()) { try { SyncZoneDir(zone); } catch (const std::exception&) { continue; } } } void ApiListener::SendConfigUpdate(const JsonRpcConnection::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 (!azone->IsChildOf(lzone)) return; Dictionary::Ptr configUpdate = new Dictionary(); String zonesDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones"; BOOST_FOREACH(const Zone::Ptr& zone, ConfigType::GetObjectsByType()) { String zoneDir = zonesDir + "/" + zone->GetName(); if (!zone->IsChildOf(azone) && !zone->IsGlobal()) { Log(LogNotice, "ApiListener") << "Skipping sync for '" << zone->GetName() << "'. Not a child of zone '" << azone->GetName() << "'."; continue; } if (!Utility::PathExists(zoneDir)) { Log(LogNotice, "ApiListener") << "Ignoring sync for '" << zone->GetName() << "'. Zone directory '" << zoneDir << "' does not exist."; continue; } Log(LogInformation, "ApiListener") << "Syncing " << (zone->IsGlobal() ? "global " : "") << "zone '" << zone->GetName() << "' to endpoint '" << endpoint->GetName() << "'."; configUpdate->Set(zone->GetName(), LoadConfigDir(zonesDir + "/" + zone->GetName())); } Dictionary::Ptr params = new Dictionary(); params->Set("update", configUpdate); Dictionary::Ptr message = new Dictionary(); message->Set("jsonrpc", "2.0"); message->Set("method", "config::Update"); message->Set("params", params); aclient->SendMessage(message); } Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) { if (!origin->FromClient->GetEndpoint() || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) return Empty; ApiListener::Ptr listener = ApiListener::GetInstance(); if (!listener) { Log(LogCritical, "ApiListener", "No instance available."); return Empty; } if (!listener->GetAcceptConfig()) { Log(LogWarning, "ApiListener") << "Ignoring config update. '" << listener->GetName() << "' does not accept config."; return Empty; } Dictionary::Ptr update = params->Get("update"); bool configChange = false; ObjectLock olock(update); BOOST_FOREACH(const Dictionary::Pair& kv, update) { Zone::Ptr zone = Zone::GetByName(kv.first); if (!zone) { Log(LogWarning, "ApiListener") << "Ignoring config update for unknown zone '" << kv.first << "'."; continue; } if (ConfigCompiler::HasZoneConfigAuthority(kv.first)) { Log(LogWarning, "ApiListener") << "Ignoring config update for zone '" << kv.first << "' because we have an authoritative version of the zone's config."; continue; } String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName(); Utility::MkDirP(oldDir, 0700); Dictionary::Ptr newConfig = kv.second; Dictionary::Ptr oldConfig = LoadConfigDir(oldDir); if (UpdateConfigDir(oldConfig, newConfig, oldDir, false)) configChange = true; } if (configChange) { Log(LogInformation, "ApiListener", "Restarting after configuration change."); Application::RequestRestart(); } return Empty; }