1 /******************************************************************************
3 * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/) *
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 "redis/rediswriter.hpp"
21 #include "redis/rediswriter.tcpp"
22 #include "remote/eventqueue.hpp"
23 #include "base/json.hpp"
25 using namespace icinga;
27 REGISTER_TYPE(RedisWriter);
29 RedisWriter::RedisWriter(void)
34 * Starts the component.
36 void RedisWriter::Start(bool runtimeCreated)
38 ObjectImpl<RedisWriter>::Start(runtimeCreated);
40 Log(LogInformation, "RedisWriter")
41 << "'" << GetName() << "' started.";
43 m_ReconnectTimer = new Timer();
44 m_ReconnectTimer->SetInterval(15);
45 m_ReconnectTimer->OnTimerExpired.connect(boost::bind(&RedisWriter::ReconnectTimerHandler, this));
46 m_ReconnectTimer->Start();
47 m_ReconnectTimer->Reschedule(0);
49 m_SubscriptionTimer = new Timer();
50 m_SubscriptionTimer->SetInterval(15);
51 m_SubscriptionTimer->OnTimerExpired.connect(boost::bind(&RedisWriter::UpdateSubscriptionsTimerHandler, this));
52 m_SubscriptionTimer->Start();
54 boost::thread thread(boost::bind(&RedisWriter::HandleEvents, this));
58 void RedisWriter::ReconnectTimerHandler(void)
60 m_WorkQueue.Enqueue(boost::bind(&RedisWriter::TryToReconnect, this));
63 void RedisWriter::TryToReconnect(void)
68 String path = GetPath();
69 String host = GetHost();
71 Log(LogInformation, "RedisWriter", "Trying to connect to redis server");
74 m_Context = redisConnect(host.CStr(), GetPort());
76 m_Context = redisConnectUnix(path.CStr());
78 if (!m_Context || m_Context->err) {
80 Log(LogWarning, "RedisWriter", "Cannot allocate redis context.");
82 Log(LogWarning, "RedisWriter", "Connection error: ")
94 String password = GetPassword();
96 if (!password.IsEmpty()) {
97 redisReply *reply = reinterpret_cast<redisReply *>(redisCommand(m_Context, "AUTH %s", password.CStr()));
100 redisFree(m_Context);
105 if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_ERROR) {
106 Log(LogInformation, "RedisWriter")
107 << "AUTH: " << reply->str;
110 freeReplyObject(reply);
114 void RedisWriter::UpdateSubscriptionsTimerHandler(void)
116 m_WorkQueue.Enqueue(boost::bind(&RedisWriter::UpdateSubscriptions, this));
119 void RedisWriter::UpdateSubscriptions(void)
124 Log(LogInformation, "RedisWriter", "Updating Redis subscriptions");
126 std::map<String, String> subscriptions;
127 long long cursor = 0;
130 redisReply *reply = reinterpret_cast<redisReply *>(redisCommand(m_Context, "SCAN %lld MATCH icinga:subscription:* COUNT 1000", cursor));
133 redisFree(m_Context);
138 if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_ERROR) {
139 Log(LogInformation, "RedisWriter")
140 << "SCAN " << cursor << " MATCH icinga:subscription:* COUNT 1000: " << reply->str;
143 VERIFY(reply->type == REDIS_REPLY_ARRAY);
144 VERIFY(reply->elements % 2 == 0);
146 redisReply *cursorReply = reply->element[0];
147 cursor = Convert::ToLong(cursorReply->str);
149 redisReply *keysReply = reply->element[1];
151 for (size_t i = 0; i < keysReply->elements; i++) {
152 redisReply *keyReply = keysReply->element[i];
153 VERIFY(keyReply->type == REDIS_REPLY_STRING);
155 redisReply *vreply = reinterpret_cast<redisReply *>(redisCommand(m_Context, "GET %s", keyReply->str));
158 freeReplyObject(reply);
159 redisFree(m_Context);
164 if (vreply->type == REDIS_REPLY_STATUS || vreply->type == REDIS_REPLY_ERROR) {
165 Log(LogInformation, "RedisWriter")
166 << "GET " << keyReply->str << ": " << vreply->str;
169 subscriptions[keyReply->str] = vreply->str;
171 freeReplyObject(vreply);
174 freeReplyObject(reply);
175 } while (cursor != 0);
177 m_Subscriptions.clear();
179 for (const std::pair<String, String>& kv : subscriptions) {
180 const String& key = kv.first.SubStr(20); /* removes the "icinga:subscription: prefix */
181 const String& value = kv.second;
184 Dictionary::Ptr subscriptionInfo = JsonDecode(value);
186 Log(LogInformation, "RedisWriter")
187 << "Subscriber Info - Key: " << key << " Value: " << Value(subscriptionInfo);
189 RedisSubscriptionInfo rsi;
191 Array::Ptr types = subscriptionInfo->Get("types");
194 rsi.EventTypes = types->ToSet<String>();
196 m_Subscriptions[key] = rsi;
197 } catch (const std::exception& ex) {
198 Log(LogWarning, "RedisWriter")
199 << "Invalid Redis subscriber info for subscriber '" << key << "': " << DiagnosticInformation(ex);
206 Log(LogInformation, "RedisWriter")
207 << "Current Redis event subscriptions: " << m_Subscriptions.size();
210 void RedisWriter::HandleEvents(void)
212 String queueName = Utility::NewUniqueID();
213 EventQueue::Ptr queue = new EventQueue(queueName);
214 EventQueue::Register(queueName, queue);
216 std::set<String> types;
217 types.insert("CheckResult");
218 types.insert("StateChange");
219 types.insert("Notification");
220 types.insert("AcknowledgementSet");
221 types.insert("AcknowledgementCleared");
222 types.insert("CommentAdded");
223 types.insert("CommentRemoved");
224 types.insert("DowntimeAdded");
225 types.insert("DowntimeRemoved");
226 types.insert("DowntimeStarted");
227 types.insert("DowntimeTriggered");
229 queue->SetTypes(types);
231 queue->AddClient(this);
234 Dictionary::Ptr event = queue->WaitForEvent(this);
239 m_WorkQueue.Enqueue(boost::bind(&RedisWriter::HandleEvent, this, event));
242 queue->RemoveClient(this);
243 EventQueue::UnregisterIfUnused(queueName, queue);
246 void RedisWriter::HandleEvent(const Dictionary::Ptr& event)
251 String type = event->Get("type");
252 bool atLeastOneSubscriber = false;
254 for (const std::pair<String, RedisSubscriptionInfo>& kv : m_Subscriptions) {
255 const auto& rsi = kv.second;
257 if (rsi.EventTypes.find(type) == rsi.EventTypes.end())
260 atLeastOneSubscriber = true;
263 if (!atLeastOneSubscriber)
266 Log(LogInformation, "RedisWriter")
267 << "Pushing event to Redis: '" << Value(event) << "'.";
269 redisReply *reply1 = reinterpret_cast<redisReply *>(redisCommand(m_Context, "INCR icinga:event.idx"));
272 redisFree(m_Context);
277 Log(LogInformation, "RedisWriter")
278 << "Called INCR in HandleEvent";
280 if (reply1->type == REDIS_REPLY_STATUS || reply1->type == REDIS_REPLY_ERROR) {
281 Log(LogInformation, "RedisWriter")
282 << "INCR icinga:event.idx: " << reply1->str;
285 if (reply1->type == REDIS_REPLY_ERROR) {
286 freeReplyObject(reply1);
291 VERIFY(reply1->type == REDIS_REPLY_INTEGER);
293 long long index = reply1->integer;
295 freeReplyObject(reply1);
297 String body = JsonEncode(event);
299 //TODO: Verify that %lld is supported
300 redisReply *reply2 = reinterpret_cast<redisReply *>(redisCommand(m_Context, "SET icinga:event.%d %s", (int)index, body.CStr()));
303 redisFree(m_Context);
308 if (reply2->type == REDIS_REPLY_STATUS || reply2->type == REDIS_REPLY_ERROR) {
309 Log(LogInformation, "RedisWriter")
310 << "SET icinga:event." << index << ": " << reply2->str;
313 if (reply2->type == REDIS_REPLY_ERROR) {
314 freeReplyObject(reply2);
318 freeReplyObject(reply2);
320 redisReply *reply3 = reinterpret_cast<redisReply *>(redisCommand(m_Context, "EXPIRE icinga:event.%d 3600", (int)index, body.CStr()));
323 redisFree(m_Context);
328 if (reply3->type == REDIS_REPLY_STATUS || reply3->type == REDIS_REPLY_ERROR) {
329 Log(LogInformation, "RedisWriter")
330 << "EXPIRE icinga:event." << index << ": " << reply3->str;
333 if (reply3->type == REDIS_REPLY_ERROR) {
334 freeReplyObject(reply3);
338 freeReplyObject(reply3);
340 for (const std::pair<String, RedisSubscriptionInfo>& kv : m_Subscriptions) {
341 const auto& name = kv.first;
342 const auto& rsi = kv.second;
344 if (rsi.EventTypes.find(type) == rsi.EventTypes.end())
347 redisReply *reply4 = reinterpret_cast<redisReply *>(redisCommand(m_Context, "LPUSH icinga:event:%s %d", name.CStr(), (int)index));
350 redisFree(m_Context);
355 if (reply4->type == REDIS_REPLY_STATUS || reply4->type == REDIS_REPLY_ERROR) {
356 Log(LogInformation, "RedisWriter")
357 << "LPUSH icinga:event:" << kv.first << " " << index << ": " << reply4->str;
360 if (reply4->type == REDIS_REPLY_ERROR) {
361 freeReplyObject(reply4);
365 freeReplyObject(reply4);
369 void RedisWriter::Stop(bool runtimeRemoved)
371 Log(LogInformation, "RedisWriter")
372 << "'" << GetName() << "' stopped.";
374 ObjectImpl<RedisWriter>::Stop(runtimeRemoved);