]> granicus.if.org Git - icinga2/blob - lib/remote/apilistener-sync.cpp
Make sure we don't include zones.d directories for zones which were removed
[icinga2] / lib / remote / apilistener-sync.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
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.                     *
9  *                                                                            *
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.                               *
14  *                                                                            *
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  ******************************************************************************/
19
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>
27 #include <fstream>
28
29 using namespace icinga;
30
31 REGISTER_APIFUNCTION(Update, config, &ApiListener::ConfigUpdateHandler);
32
33 bool ApiListener::IsConfigMaster(const Zone::Ptr& zone) const
34 {
35         String path = Application::GetZonesDir() + "/" + zone->GetName();
36         return Utility::PathExists(path);
37 }
38
39 void ApiListener::ConfigGlobHandler(Dictionary::Ptr& config, const String& path, const String& file)
40 {
41         CONTEXT("Creating config update for file '" + file + "'");
42
43         Log(LogNotice, "ApiListener")
44             << "Creating config update for file '" << file << "'";
45
46         std::ifstream fp(file.CStr());
47         if (!fp)
48                 return;
49
50         String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
51         config->Set(file.SubStr(path.GetLength()), content);
52 }
53
54 Dictionary::Ptr ApiListener::LoadConfigDir(const String& dir)
55 {
56         Dictionary::Ptr config = new Dictionary();
57         Utility::GlobRecursive(dir, "*.conf", boost::bind(&ApiListener::ConfigGlobHandler, boost::ref(config), dir, _1), GlobFile);
58         return config;
59 }
60
61 bool ApiListener::UpdateConfigDir(const Dictionary::Ptr& oldConfig, const Dictionary::Ptr& newConfig, const String& configDir, bool authoritative)
62 {
63         bool configChange = false;
64
65         if (oldConfig->Contains(".timestamp") && newConfig->Contains(".timestamp")) {
66                 double oldTS = Convert::ToDouble(oldConfig->Get(".timestamp"));
67                 double newTS = Convert::ToDouble(newConfig->Get(".timestamp"));
68
69                 /* skip update if our config is newer */
70                 if (oldTS <= newTS)
71                         return false;
72         }
73
74         BOOST_FOREACH(const Dictionary::Pair& kv, newConfig) {
75                 if (oldConfig->Get(kv.first) != kv.second) {
76                         configChange = true;
77
78                         String path = configDir + "/" + kv.first;
79                         Log(LogInformation, "ApiListener")
80                             << "Updating configuration file: " << path;
81
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);
85                         fp << kv.second;
86                         fp.close();
87                 }
88         }
89
90         BOOST_FOREACH(const Dictionary::Pair& kv, oldConfig) {
91                 if (!newConfig->Contains(kv.first)) {
92                         configChange = true;
93
94                         String path = configDir + "/" + kv.first;
95                         (void) unlink(path.CStr());
96                 }
97         }
98
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();
103                 fp.close();
104         }
105
106         if (authoritative) {
107                 String authPath = configDir + "/.authoritative";
108                 if (!Utility::PathExists(tsPath)) {
109                         std::ofstream fp(tsPath.CStr(), std::ofstream::out | std::ostream::trunc);
110                         fp.close();
111                 }
112         }
113
114         return configChange;
115 }
116
117 void ApiListener::SyncZoneDir(const Zone::Ptr& zone) const
118 {
119         String newDir = Application::GetZonesDir() + "/" + zone->GetName();
120         String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
121
122         Log(LogInformation, "ApiListener")
123             << "Copying zone configuration files from '" << newDir << "' to  '" << oldDir << "'.";
124
125         if (!Utility::MkDir(oldDir, 0700)) {
126                 Log(LogCritical, "ApiListener")
127                     << "mkdir() for path '" << oldDir << "'failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
128
129                 BOOST_THROW_EXCEPTION(posix_error()
130                         << boost::errinfo_api_function("mkdir")
131                         << boost::errinfo_errno(errno)
132                         << boost::errinfo_file_name(oldDir));
133         }
134
135         Dictionary::Ptr newConfig = LoadConfigDir(newDir);
136         Dictionary::Ptr oldConfig = LoadConfigDir(oldDir);
137
138         UpdateConfigDir(oldConfig, newConfig, oldDir, true);
139 }
140
141 void ApiListener::SyncZoneDirs(void) const
142 {
143         BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjectsByType<Zone>()) {
144                 if (!IsConfigMaster(zone))
145                         continue;
146
147                 try {
148                         SyncZoneDir(zone);
149                 } catch (const std::exception&) {
150                         continue;
151                 }
152         }
153 }
154
155 void ApiListener::SendConfigUpdate(const ApiClient::Ptr& aclient)
156 {
157         Endpoint::Ptr endpoint = aclient->GetEndpoint();
158         ASSERT(endpoint);
159
160         Zone::Ptr azone = endpoint->GetZone();
161         Zone::Ptr lzone = Zone::GetLocalZone();
162
163         /* don't try to send config updates to our master */
164         if (!azone->IsChildOf(lzone))
165                 return;
166
167         Dictionary::Ptr configUpdate = new Dictionary();
168
169         String zonesDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
170
171         BOOST_FOREACH(const Zone::Ptr& zone, DynamicType::GetObjectsByType<Zone>()) {
172                 String zoneDir = zonesDir + "/" + zone->GetName();
173
174                 if (!zone->IsChildOf(azone) && !zone->IsGlobal()) {
175                         Log(LogNotice, "ApiListener")
176                             << "Skipping sync for '" << zone->GetName() << "'. Not a child of zone '" << azone->GetName() << "'.";
177                         continue;
178                 }
179                 if (!Utility::PathExists(zoneDir)) {
180                         Log(LogNotice, "ApiListener")
181                             << "Ignoring sync for '" << zone->GetName() << "'. Zone directory '" << zoneDir << "' does not exist.";
182                         continue;
183                 }
184
185                 if (zone->IsGlobal())
186                         Log(LogInformation, "ApiListener")
187                             << "Syncing global zone '" << zone->GetName() << "'.";
188
189                 configUpdate->Set(zone->GetName(), LoadConfigDir(zonesDir + "/" + zone->GetName()));
190         }
191
192         Dictionary::Ptr params = new Dictionary();
193         params->Set("update", configUpdate);
194
195         Dictionary::Ptr message = new Dictionary();
196         message->Set("jsonrpc", "2.0");
197         message->Set("method", "config::Update");
198         message->Set("params", params);
199
200         aclient->SendMessage(message);
201 }
202
203 Value ApiListener::ConfigUpdateHandler(const MessageOrigin& origin, const Dictionary::Ptr& params)
204 {
205         if (!origin.FromClient->GetEndpoint() || (origin.FromZone && !Zone::GetLocalZone()->IsChildOf(origin.FromZone)))
206                 return Empty;
207
208         ApiListener::Ptr listener = ApiListener::GetInstance();
209
210         if (!listener) {
211                 Log(LogCritical, "ApiListener", "No instance available.");
212                 return Empty;
213         }
214
215         if (!listener->GetAcceptConfig()) {
216                 Log(LogWarning, "ApiListener")
217                     << "Ignoring config update. '" << listener->GetName() << "' does not accept config.";
218                 return Empty;
219         }
220
221         Dictionary::Ptr update = params->Get("update");
222
223         bool configChange = false;
224
225         ObjectLock olock(update);
226         BOOST_FOREACH(const Dictionary::Pair& kv, update) {
227                 Zone::Ptr zone = Zone::GetByName(kv.first);
228
229                 if (!zone) {
230                         Log(LogWarning, "ApiListener")
231                             << "Ignoring config update for unknown zone: " << kv.first;
232                         continue;
233                 }
234
235                 String oldDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones/" + zone->GetName();
236
237                 if (!Utility::MkDir(oldDir, 0700)) {
238                         Log(LogCritical, "ApiListener")
239                             << "mkdir() for path '" << oldDir << "'failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
240
241                         BOOST_THROW_EXCEPTION(posix_error()
242                                 << boost::errinfo_api_function("mkdir")
243                                 << boost::errinfo_errno(errno)
244                                 << boost::errinfo_file_name(oldDir));
245                 }
246
247                 Dictionary::Ptr newConfig = kv.second;
248                 Dictionary::Ptr oldConfig = LoadConfigDir(oldDir);
249
250                 if (UpdateConfigDir(oldConfig, newConfig, oldDir, false))
251                         configChange = true;
252         }
253
254         if (configChange) {
255                 Log(LogInformation, "ApiListener", "Restarting after configuration change.");
256                 Application::RequestRestart();
257         }
258
259         return Empty;
260 }