1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "remote/apilistener.hpp"
4 #include "remote/apifunction.hpp"
5 #include "config/configcompiler.hpp"
6 #include "base/configtype.hpp"
7 #include "base/logger.hpp"
8 #include "base/convert.hpp"
9 #include "base/exception.hpp"
10 #include "base/utility.hpp"
14 using namespace icinga;
16 REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler);
18 void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file)
20 CONTEXT("Creating config update for file '" + file + "'");
22 Log(LogNotice, "ApiListener")
23 << "Creating config update for file '" << file << "'.";
25 std::ifstream fp(file.CStr(), std::ifstream::binary);
29 String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
31 Dictionary::Ptr update;
33 if (Utility::Match("*.conf", file))
34 update = config.UpdateV1;
36 update = config.UpdateV2;
38 update->Set(file.SubStr(path.GetLength()), content);
41 Dictionary::Ptr ApiListener::MergeConfigUpdate(const ConfigDirInformation& config)
43 Dictionary::Ptr result = new Dictionary();
46 config.UpdateV1->CopyTo(result);
49 config.UpdateV2->CopyTo(result);
54 ConfigDirInformation ApiListener::LoadConfigDir(const String& dir)
56 ConfigDirInformation config;
57 config.UpdateV1 = new Dictionary();
58 config.UpdateV2 = new Dictionary();
59 Utility::GlobRecursive(dir, "*", std::bind(&ApiListener::ConfigGlobHandler, std::ref(config), dir, _1), GlobFile);
63 bool ApiListener::UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, const ConfigDirInformation& newConfigInfo, const String& configDir, bool authoritative)
65 bool configChange = false;
67 Dictionary::Ptr oldConfig = MergeConfigUpdate(oldConfigInfo);
68 Dictionary::Ptr newConfig = MergeConfigUpdate(newConfigInfo);
72 if (!oldConfig->Contains("/.timestamp"))
75 oldTimestamp = oldConfig->Get("/.timestamp");
79 if (!newConfig->Contains("/.timestamp"))
80 newTimestamp = Utility::GetTime();
82 newTimestamp = newConfig->Get("/.timestamp");
84 /* skip update if our configuration files are more recent */
85 if (oldTimestamp >= newTimestamp) {
86 Log(LogNotice, "ApiListener")
87 << "Our configuration is more recent than the received configuration update."
88 << " Ignoring configuration file update for path '" << configDir << "'. Current timestamp '"
89 << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", oldTimestamp) << "' ("
90 << std::fixed << std::setprecision(6) << oldTimestamp
91 << ") >= received timestamp '"
92 << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' ("
93 << newTimestamp << ").";
100 ObjectLock olock(newConfig);
101 for (const Dictionary::Pair& kv : newConfig) {
102 if (oldConfig->Get(kv.first) != kv.second) {
103 if (!Utility::Match("*/.timestamp", kv.first))
106 String path = configDir + "/" + kv.first;
107 Log(LogInformation, "ApiListener")
108 << "Updating configuration file: " << path;
110 /* Sync string content only. */
111 String content = kv.second;
113 /* Generate a directory tree (zones/1/2/3 might not exist yet). */
114 Utility::MkDirP(Utility::DirName(path), 0755);
115 std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
119 numBytes += content.GetLength();
124 Log(LogInformation, "ApiListener")
125 << "Applying configuration file update for path '" << configDir << "' (" << numBytes << " Bytes). Received timestamp '"
126 << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' ("
127 << std::fixed << std::setprecision(6) << newTimestamp
128 << "), Current timestamp '"
129 << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", oldTimestamp) << "' ("
130 << oldTimestamp << ").";
132 ObjectLock xlock(oldConfig);
133 for (const Dictionary::Pair& kv : oldConfig) {
134 if (!newConfig->Contains(kv.first)) {
137 String path = configDir + "/" + kv.first;
138 (void) unlink(path.CStr());
142 String tsPath = configDir + "/.timestamp";
143 if (!Utility::PathExists(tsPath)) {
144 std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
145 fp << std::fixed << newTimestamp;
150 String authPath = configDir + "/.authoritative";
151 if (!Utility::PathExists(authPath)) {
152 std::ofstream fp(authPath.CStr(), std::ofstream::out | std::ostream::trunc);
160 void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
162 ConfigDirInformation newConfigInfo;
163 newConfigInfo.UpdateV1 = new Dictionary();
164 newConfigInfo.UpdateV2 = new Dictionary();
166 for (const ZoneFragment& zf : ConfigCompiler::GetZoneDirs(zone->GetName())) {
167 ConfigDirInformation newConfigPart = LoadConfigDir(zf.Path);
170 ObjectLock olock(newConfigPart.UpdateV1);
171 for (const Dictionary::Pair& kv : newConfigPart.UpdateV1) {
172 newConfigInfo.UpdateV1->Set("/" + zf.Tag + kv.first, kv.second);
177 ObjectLock olock(newConfigPart.UpdateV2);
178 for (const Dictionary::Pair& kv : newConfigPart.UpdateV2) {
179 newConfigInfo.UpdateV2->Set("/" + zf.Tag + kv.first, kv.second);
184 int sumUpdates = newConfigInfo.UpdateV1->GetLength() + newConfigInfo.UpdateV2->GetLength();
189 String oldDir = Configuration::DataDir + "/api/zones/" + zone->GetName();
191 Log(LogInformation, "ApiListener")
192 << "Copying " << sumUpdates << " zone configuration files for zone '" << zone->GetName() << "' to '" << oldDir << "'.";
194 Utility::MkDirP(oldDir, 0700);
196 ConfigDirInformation oldConfigInfo = LoadConfigDir(oldDir);
198 UpdateConfigDir(oldConfigInfo, newConfigInfo, oldDir, true);
201 void ApiListener::SyncZoneDirs() const
203 for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
206 } catch (const std::exception&) {
212 void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
214 Endpoint::Ptr endpoint = aclient->GetEndpoint();
217 Zone::Ptr azone = endpoint->GetZone();
218 Zone::Ptr lzone = Zone::GetLocalZone();
220 /* don't try to send config updates to our master */
221 if (!azone->IsChildOf(lzone))
224 Dictionary::Ptr configUpdateV1 = new Dictionary();
225 Dictionary::Ptr configUpdateV2 = new Dictionary();
227 String zonesDir = Configuration::DataDir + "/api/zones";
229 for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
230 String zoneDir = zonesDir + "/" + zone->GetName();
232 if (!zone->IsChildOf(azone) && !zone->IsGlobal())
235 if (!Utility::PathExists(zoneDir))
238 Log(LogInformation, "ApiListener")
239 << "Syncing configuration files for " << (zone->IsGlobal() ? "global " : "")
240 << "zone '" << zone->GetName() << "' to endpoint '" << endpoint->GetName() << "'.";
242 ConfigDirInformation config = LoadConfigDir(zonesDir + "/" + zone->GetName());
243 configUpdateV1->Set(zone->GetName(), config.UpdateV1);
244 configUpdateV2->Set(zone->GetName(), config.UpdateV2);
247 Dictionary::Ptr message = new Dictionary({
248 { "jsonrpc", "2.0" },
249 { "method", "config::Update" },
250 { "params", new Dictionary({
251 { "update", configUpdateV1 },
252 { "update_v2", configUpdateV2 }
256 aclient->SendMessage(message);
259 Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
261 if (!origin->FromClient->GetEndpoint() || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone)))
264 ApiListener::Ptr listener = ApiListener::GetInstance();
267 Log(LogCritical, "ApiListener", "No instance available.");
271 if (!listener->GetAcceptConfig()) {
272 Log(LogWarning, "ApiListener")
273 << "Ignoring config update. '" << listener->GetName() << "' does not accept config.";
277 Log(LogInformation, "ApiListener")
278 << "Applying config update from endpoint '" << origin->FromClient->GetEndpoint()->GetName()
279 << "' of zone '" << GetFromZoneName(origin->FromZone) << "'.";
281 Dictionary::Ptr updateV1 = params->Get("update");
282 Dictionary::Ptr updateV2 = params->Get("update_v2");
284 bool configChange = false;
286 ObjectLock olock(updateV1);
287 for (const Dictionary::Pair& kv : updateV1) {
288 Zone::Ptr zone = Zone::GetByName(kv.first);
291 Log(LogWarning, "ApiListener")
292 << "Ignoring config update for unknown zone '" << kv.first << "'.";
296 if (ConfigCompiler::HasZoneConfigAuthority(kv.first)) {
297 Log(LogWarning, "ApiListener")
298 << "Ignoring config update for zone '" << kv.first << "' because we have an authoritative version of the zone's config.";
302 String oldDir = Configuration::DataDir + "/api/zones/" + zone->GetName();
304 Utility::MkDirP(oldDir, 0700);
306 ConfigDirInformation newConfigInfo;
307 newConfigInfo.UpdateV1 = kv.second;
310 newConfigInfo.UpdateV2 = updateV2->Get(kv.first);
312 Dictionary::Ptr newConfig = kv.second;
313 ConfigDirInformation oldConfigInfo = LoadConfigDir(oldDir);
315 if (UpdateConfigDir(oldConfigInfo, newConfigInfo, oldDir, false))
320 Log(LogInformation, "ApiListener", "Restarting after configuration change.");
321 Application::RequestRestart();