1 /******************************************************************************
3 * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "remote/apilistener.hpp"
21 #include "remote/apifunction.hpp"
22 #include "base/dynamictype.hpp"
23 #include "base/logger.hpp"
24 #include "base/convert.hpp"
25 #include "base/exception.hpp"
26 #include <boost/foreach.hpp>
29 using namespace icinga;
31 REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler);
33 bool ApiListener::IsConfigMaster(const Zone::Ptr& zone) const
35 String path = Application::GetZonesDir() + "/" + zone->GetName();
36 return Utility::PathExists(path);
39 void ApiListener::ConfigGlobHandler(Dictionary::Ptr& config, const String& path, const String& file)
41 CONTEXT("Creating config update for file '" + file + "'");
43 Log(LogNotice, "ApiListener")
44 << "Creating config update for file '" << file << "'";
46 std::ifstream fp(file.CStr());
50 String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
51 config->Set(file.SubStr(path.GetLength()), content);
54 Dictionary::Ptr ApiListener::LoadConfigDir(const String& dir)
56 Dictionary::Ptr config = new Dictionary();
57 Utility::GlobRecursive(dir, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, boost::ref(config), dir, _1), GlobFile);
61 bool ApiListener::UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictionary::Ptr& newConfig, const String& configDir, bool authoritative)
63 bool configChange = false;
65 if (oldConfig->Contains(".timestamp") && newConfig->Contains(".timestamp")) {
66 double oldTS = Convert::ToDouble(oldConfig->Get(".timestamp"));
67 double newTS = Convert::ToDouble(newConfig->Get(".timestamp"));
69 /* skip update if our config is newer */
74 BOOST_FOREACH(const Dictionary::Pair& kv, newConfig) {
75 if (oldConfig->Get(kv.first) != kv.second) {
78 String path = configDir + "/" + kv.first;
79 Log(LogInformation, "ApiListener")
80 << "Updating configuration file: " << path;
82 //pass the directory and generate a dir tree, if not existing already
83 Utility::MkDirP(Utility::DirName(path), 0755);
84 std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::trunc);
90 BOOST_FOREACH(const Dictionary::Pair& kv, oldConfig) {
91 if (!newConfig->Contains(kv.first)) {
94 String path = configDir + "/" + kv.first;
95 (void) unlink(path.CStr());
99 String tsPath = configDir + "/.timestamp";
100 if (!Utility::PathExists(tsPath)) {
101 std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
102 fp << Utility::GetTime();
107 String authPath = configDir + "/.authoritative";
108 if (!Utility::PathExists(tsPath)) {
109 std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
117 void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
119 String newDir = Application::GetZonesDir() + "/" + zone->GetName();
120 String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
122 Log(LogInformation, "ApiListener")
123 << "Copying zone configuration files from '" << newDir << "' to '" << oldDir << "'.";
125 if (!Utility::MkDir(oldDir, 0700)) {
126 Log(LogCritical, "ApiListener")
127 << "mkdir() for path '" << oldDir << "'failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
129 BOOST_THROW_EXCEPTION(posix_error()
130 << boost::errinfo_api_function("mkdir")
131 << boost::errinfo_errno(errno)
132 << boost::errinfo_file_name(oldDir));
135 Dictionary::Ptr newConfig = LoadConfigDir(newDir);
136 Dictionary::Ptr oldConfig = LoadConfigDir(oldDir);
138 UpdateConfigDir(oldConfig, newConfig, oldDir, true);
141 void ApiListener::SyncZoneDirs(void) const
143 BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjectsByType<Zone>()) {
144 if (!IsConfigMaster(zone))
149 } catch (const std::exception&) {
155 void ApiListener::SendConfigUpdate(const ApiClient::Ptr& aclient)
157 Endpoint::Ptr endpoint = aclient->GetEndpoint();
160 Zone::Ptr azone = endpoint->GetZone();
161 Zone::Ptr lzone = Zone::GetLocalZone();
163 /* don't try to send config updates to our master */
164 if (!azone->IsChildOf(lzone))
167 Dictionary::Ptr configUpdate = new Dictionary();
169 String zonesDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
171 BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjectsByType<Zone>()) {
172 String zoneDir = zonesDir + "/" + zone->GetName();
174 if (!zone->IsChildOf(azone) && !zone->IsGlobal()) {
175 Log(LogNotice, "ApiListener")
176 << "Skipping sync for '" << zone->GetName() << "'. Not a child of zone '" << azone->GetName() << "'.";
179 if (!Utility::PathExists(zoneDir)) {
180 Log(LogNotice, "ApiListener")
181 << "Ignoring sync for '" << zone->GetName() << "'. Zone directory '" << zoneDir << "' does not exist.";
185 if (zone->IsGlobal())
186 Log(LogInformation, "ApiListener")
187 << "Syncing global zone '" << zone->GetName() << "'.";
189 configUpdate->Set(zone->GetName(), LoadConfigDir(zonesDir + "/" + zone->GetName()));
192 Dictionary::Ptr params = new Dictionary();
193 params->Set("update", configUpdate);
195 Dictionary::Ptr message = new Dictionary();
196 message->Set("jsonrpc", "2.0");
197 message->Set("method", "config::Update");
198 message->Set("params", params);
200 aclient->SendMessage(message);
203 Value ApiListener::ConfigUpdateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
205 if (!origin.FromClient->GetEndpoint() || (origin.FromZone && !Zone::GetLocalZone()->IsChildOf(origin.FromZone)))
208 ApiListener::Ptr listener = ApiListener::GetInstance();
211 Log(LogCritical, "ApiListener", "No instance available.");
215 if (!listener->GetAcceptConfig()) {
216 Log(LogWarning, "ApiListener")
217 << "Ignoring config update. '" << listener->GetName() << "' does not accept config.";
221 Dictionary::Ptr update = params->Get("update");
223 bool configChange = false;
225 ObjectLock olock(update);
226 BOOST_FOREACH(const Dictionary::Pair& kv, update) {
227 Zone::Ptr zone = Zone::GetByName(kv.first);
230 Log(LogWarning, "ApiListener")
231 << "Ignoring config update for unknown zone: " << kv.first;
235 String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
237 if (!Utility::MkDir(oldDir, 0700)) {
238 Log(LogCritical, "ApiListener")
239 << "mkdir() for path '" << oldDir << "'failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
241 BOOST_THROW_EXCEPTION(posix_error()
242 << boost::errinfo_api_function("mkdir")
243 << boost::errinfo_errno(errno)
244 << boost::errinfo_file_name(oldDir));
247 Dictionary::Ptr newConfig = kv.second;
248 Dictionary::Ptr oldConfig = LoadConfigDir(oldDir);
250 if (UpdateConfigDir(oldConfig, newConfig, oldDir, false))
255 Log(LogInformation, "ApiListener", "Restarting after configuration change.");
256 Application::RequestRestart();