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