]> granicus.if.org Git - icinga2/blob - lib/remote/apilistener-filesync.cpp
Merge pull request #6999 from Icinga/bugfix/compiler-warnings
[icinga2] / lib / remote / apilistener-filesync.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
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 <fstream>
11 #include <iomanip>
12
13 using namespace icinga;
14
15 REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler);
16
17 void ApiListener::ConfigGlobHandler(ConfigDirInformation& config, const String& path, const String& file)
18 {
19         CONTEXT("Creating config update for file '" + file + "'");
20
21         Log(LogNotice, "ApiListener")
22                 << "Creating config update for file '" << file << "'.";
23
24         std::ifstream fp(file.CStr(), std::ifstream::binary);
25         if (!fp)
26                 return;
27
28         String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
29
30         Dictionary::Ptr update;
31
32         if (Utility::Match("*.conf", file))
33                 update = config.UpdateV1;
34         else
35                 update = config.UpdateV2;
36
37         update->Set(file.SubStr(path.GetLength()), content);
38 }
39
40 Dictionary::Ptr ApiListener::MergeConfigUpdate(const ConfigDirInformation& config)
41 {
42         Dictionary::Ptr result = new Dictionary();
43
44         if (config.UpdateV1)
45                 config.UpdateV1->CopyTo(result);
46
47         if (config.UpdateV2)
48                 config.UpdateV2->CopyTo(result);
49
50         return result;
51 }
52
53 ConfigDirInformation ApiListener::LoadConfigDir(const String& dir)
54 {
55         ConfigDirInformation config;
56         config.UpdateV1 = new Dictionary();
57         config.UpdateV2 = new Dictionary();
58         Utility::GlobRecursive(dir, "*", std::bind(&ApiListener::ConfigGlobHandler, std::ref(config), dir, _1), GlobFile);
59         return config;
60 }
61
62 bool ApiListener::UpdateConfigDir(const ConfigDirInformation& oldConfigInfo, const ConfigDirInformation& newConfigInfo, const String& configDir, bool authoritative)
63 {
64         bool configChange = false;
65
66         Dictionary::Ptr oldConfig = MergeConfigUpdate(oldConfigInfo);
67         Dictionary::Ptr newConfig = MergeConfigUpdate(newConfigInfo);
68
69         double oldTimestamp;
70
71         if (!oldConfig->Contains("/.timestamp"))
72                 oldTimestamp = 0;
73         else
74                 oldTimestamp = oldConfig->Get("/.timestamp");
75
76         double newTimestamp;
77
78         if (!newConfig->Contains("/.timestamp"))
79                 newTimestamp = Utility::GetTime();
80         else
81                 newTimestamp = newConfig->Get("/.timestamp");
82
83         /* skip update if our configuration files are more recent */
84         if (oldTimestamp >= newTimestamp) {
85                 Log(LogNotice, "ApiListener")
86                         << "Our configuration is more recent than the received configuration update."
87                         << " Ignoring configuration file update for path '" << configDir << "'. Current timestamp '"
88                         << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", oldTimestamp) << "' ("
89                         << std::fixed << std::setprecision(6) << oldTimestamp
90                         << ") >= received timestamp '"
91                         << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' ("
92                         << newTimestamp << ").";
93                 return false;
94         }
95
96         size_t numBytes = 0;
97
98         {
99                 ObjectLock olock(newConfig);
100                 for (const Dictionary::Pair& kv : newConfig) {
101                         if (oldConfig->Get(kv.first) != kv.second) {
102                                 if (!Utility::Match("*/.timestamp", kv.first))
103                                         configChange = true;
104
105                                 String path = configDir + "/" + kv.first;
106                                 Log(LogInformation, "ApiListener")
107                                         << "Updating configuration file: " << path;
108
109                                 /* Sync string content only. */
110                                 String content = kv.second;
111
112                                 /* Generate a directory tree (zones/1/2/3 might not exist yet). */
113                                 Utility::MkDirP(Utility::DirName(path), 0755);
114                                 std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
115                                 fp << content;
116                                 fp.close();
117
118                                 numBytes += content.GetLength();
119                         }
120                 }
121         }
122
123         Log(LogInformation, "ApiListener")
124                 << "Applying configuration file update for path '" << configDir << "' (" << numBytes << " Bytes). Received timestamp '"
125                 << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", newTimestamp) << "' ("
126                 << std::fixed << std::setprecision(6) << newTimestamp
127                 << "), Current timestamp '"
128                 << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", oldTimestamp) << "' ("
129                 << oldTimestamp << ").";
130
131         ObjectLock xlock(oldConfig);
132         for (const Dictionary::Pair& kv : oldConfig) {
133                 if (!newConfig->Contains(kv.first)) {
134                         configChange = true;
135
136                         String path = configDir + "/" + kv.first;
137                         (void) unlink(path.CStr());
138                 }
139         }
140
141         String tsPath = configDir + "/.timestamp";
142         if (!Utility::PathExists(tsPath)) {
143                 std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
144                 fp << std::fixed << newTimestamp;
145                 fp.close();
146         }
147
148         if (authoritative) {
149                 String authPath = configDir + "/.authoritative";
150                 if (!Utility::PathExists(authPath)) {
151                         std::ofstream fp(authPath.CStr(), std::ofstream::out | std::ostream::trunc);
152                         fp.close();
153                 }
154         }
155
156         return configChange;
157 }
158
159 void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
160 {
161         ConfigDirInformation newConfigInfo;
162         newConfigInfo.UpdateV1 = new Dictionary();
163         newConfigInfo.UpdateV2 = new Dictionary();
164
165         for (const ZoneFragment& zf : ConfigCompiler::GetZoneDirs(zone->GetName())) {
166                 ConfigDirInformation newConfigPart = LoadConfigDir(zf.Path);
167
168                 {
169                         ObjectLock olock(newConfigPart.UpdateV1);
170                         for (const Dictionary::Pair& kv : newConfigPart.UpdateV1) {
171                                 newConfigInfo.UpdateV1->Set("/" + zf.Tag + kv.first, kv.second);
172                         }
173                 }
174
175                 {
176                         ObjectLock olock(newConfigPart.UpdateV2);
177                         for (const Dictionary::Pair& kv : newConfigPart.UpdateV2) {
178                                 newConfigInfo.UpdateV2->Set("/" + zf.Tag + kv.first, kv.second);
179                         }
180                 }
181         }
182
183         int sumUpdates = newConfigInfo.UpdateV1->GetLength() + newConfigInfo.UpdateV2->GetLength();
184
185         if (sumUpdates == 0)
186                 return;
187
188         String oldDir = Configuration::DataDir + "/api/zones/" + zone->GetName();
189
190         Log(LogInformation, "ApiListener")
191                 << "Copying " << sumUpdates << " zone configuration files for zone '" << zone->GetName() << "' to '" << oldDir << "'.";
192
193         Utility::MkDirP(oldDir, 0700);
194
195         ConfigDirInformation oldConfigInfo = LoadConfigDir(oldDir);
196
197         UpdateConfigDir(oldConfigInfo, newConfigInfo, oldDir, true);
198 }
199
200 void ApiListener::SyncZoneDirs() const
201 {
202         for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
203                 try {
204                         SyncZoneDir(zone);
205                 } catch (const std::exception&) {
206                         continue;
207                 }
208         }
209 }
210
211 void ApiListener::SendConfigUpdate(const JsonRpcConnection::Ptr& aclient)
212 {
213         Endpoint::Ptr endpoint = aclient->GetEndpoint();
214         ASSERT(endpoint);
215
216         Zone::Ptr azone = endpoint->GetZone();
217         Zone::Ptr lzone = Zone::GetLocalZone();
218
219         /* don't try to send config updates to our master */
220         if (!azone->IsChildOf(lzone))
221                 return;
222
223         Dictionary::Ptr configUpdateV1 = new Dictionary();
224         Dictionary::Ptr configUpdateV2 = new Dictionary();
225
226         String zonesDir = Configuration::DataDir + "/api/zones";
227
228         for (const Zone::Ptr& zone : ConfigType::GetObjectsByType<Zone>()) {
229                 String zoneDir = zonesDir + "/" + zone->GetName();
230
231                 if (!zone->IsChildOf(azone) && !zone->IsGlobal())
232                         continue;
233
234                 if (!Utility::PathExists(zoneDir))
235                         continue;
236
237                 Log(LogInformation, "ApiListener")
238                         << "Syncing configuration files for " << (zone->IsGlobal() ? "global " : "")
239                         << "zone '" << zone->GetName() << "' to endpoint '" << endpoint->GetName() << "'.";
240
241                 ConfigDirInformation config = LoadConfigDir(zonesDir + "/" + zone->GetName());
242                 configUpdateV1->Set(zone->GetName(), config.UpdateV1);
243                 configUpdateV2->Set(zone->GetName(), config.UpdateV2);
244         }
245
246         Dictionary::Ptr message = new Dictionary({
247                 { "jsonrpc", "2.0" },
248                 { "method", "config::Update" },
249                 { "params", new Dictionary({
250                         { "update", configUpdateV1 },
251                         { "update_v2", configUpdateV2 }
252                 }) }
253         });
254
255         aclient->SendMessage(message);
256 }
257
258 Value ApiListener::ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
259 {
260         if (!origin->FromClient->GetEndpoint() || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone)))
261                 return Empty;
262
263         ApiListener::Ptr listener = ApiListener::GetInstance();
264
265         if (!listener) {
266                 Log(LogCritical, "ApiListener", "No instance available.");
267                 return Empty;
268         }
269
270         if (!listener->GetAcceptConfig()) {
271                 Log(LogWarning, "ApiListener")
272                         << "Ignoring config update. '" << listener->GetName() << "' does not accept config.";
273                 return Empty;
274         }
275
276         Log(LogInformation, "ApiListener")
277                 << "Applying config update from endpoint '" << origin->FromClient->GetEndpoint()->GetName()
278                 << "' of zone '" << GetFromZoneName(origin->FromZone) << "'.";
279
280         Dictionary::Ptr updateV1 = params->Get("update");
281         Dictionary::Ptr updateV2 = params->Get("update_v2");
282
283         bool configChange = false;
284
285         ObjectLock olock(updateV1);
286         for (const Dictionary::Pair& kv : updateV1) {
287                 Zone::Ptr zone = Zone::GetByName(kv.first);
288
289                 if (!zone) {
290                         Log(LogWarning, "ApiListener")
291                                 << "Ignoring config update for unknown zone '" << kv.first << "'.";
292                         continue;
293                 }
294
295                 if (ConfigCompiler::HasZoneConfigAuthority(kv.first)) {
296                         Log(LogWarning, "ApiListener")
297                                 << "Ignoring config update for zone '" << kv.first << "' because we have an authoritative version of the zone's config.";
298                         continue;
299                 }
300
301                 String oldDir = Configuration::DataDir + "/api/zones/" + zone->GetName();
302
303                 Utility::MkDirP(oldDir, 0700);
304
305                 ConfigDirInformation newConfigInfo;
306                 newConfigInfo.UpdateV1 = kv.second;
307
308                 if (updateV2)
309                         newConfigInfo.UpdateV2 = updateV2->Get(kv.first);
310
311                 Dictionary::Ptr newConfig = kv.second;
312                 ConfigDirInformation oldConfigInfo = LoadConfigDir(oldDir);
313
314                 if (UpdateConfigDir(oldConfigInfo, newConfigInfo, oldDir, false))
315                         configChange = true;
316         }
317
318         if (configChange) {
319                 Log(LogInformation, "ApiListener", "Restarting after configuration change.");
320                 Application::RequestRestart();
321         }
322
323         return Empty;
324 }