]> granicus.if.org Git - icinga2/blob - lib/redis/rediswriter-config.cpp
Add ASSERTs for the Redis queries
[icinga2] / lib / redis / rediswriter-config.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2017 Icinga Development Team (https://www.icinga.com/)  *
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 "redis/rediswriter.hpp"
21 #include "icinga/customvarobject.hpp"
22 #include "base/json.hpp"
23 #include "base/logger.hpp"
24 #include "base/serializer.hpp"
25 #include "base/initialize.hpp"
26
27 using namespace icinga;
28
29 /*
30 - icinga:config:<type> as hash
31 key: sha1 checksum(name)
32 value: JsonEncode(Serialize(object, FAConfig)) + config_checksum
33
34 Diff between calculated config_checksum and Redis json config_checksum
35 Alternative: Replace into.
36
37
38 - icinga:status:<type> as hash
39 key: sha1 checksum(name)
40 value: JsonEncode(Serialize(object, FAState))
41 */
42
43 INITIALIZE_ONCE(&RedisWriter::ConfigStaticInitialize);
44
45 void RedisWriter::ConfigStaticInitialize(void)
46 {
47         /* triggered in ProcessCheckResult(), requires UpdateNextCheck() to be called before */
48         ConfigObject::OnStateChanged.connect(boost::bind(&RedisWriter::StateChangedHandler, _1));
49         CustomVarObject::OnVarsChanged.connect(boost::bind(&RedisWriter::VarsChangedHandler, _1));
50
51         /* triggered on create, update and delete objects */
52         ConfigObject::OnVersionChanged.connect(boost::bind(&RedisWriter::VersionChangedHandler, _1));
53 }
54
55 //TODO: OnActiveChanged handling.
56 void RedisWriter::UpdateAllConfigObjects(void)
57 {
58         AssertOnWorkQueue();
59
60         //TODO: Just use config types
61         for (const Type::Ptr& type : Type::GetAllTypes()) {
62                 if (!ConfigObject::TypeInstance->IsAssignableFrom(type))
63                         continue;
64
65                 String typeName = type->GetName();
66
67                 /* replace into aka delete insert is faster than a full diff */
68                 redisReply *reply1 = reinterpret_cast<redisReply *>(redisCommand(m_Context, "DEL icinga:config:%s", typeName.CStr()));
69
70                 if (!reply1) {
71                         redisFree(m_Context);
72                         m_Context = NULL;
73                         return;
74                 }
75
76                 if (reply1->type == REDIS_REPLY_STATUS || reply1->type == REDIS_REPLY_ERROR) {
77                         Log(LogInformation, "RedisWriter")
78                             << "DEL icinga:config:" << typeName << ": " << reply1->str;
79                 }
80
81                 if (reply1->type == REDIS_REPLY_ERROR) {
82                         freeReplyObject(reply1);
83                         return;
84                 }
85
86                 freeReplyObject(reply1);
87
88                 redisReply *reply2 = reinterpret_cast<redisReply *>(redisCommand(m_Context, "DEL icinga:status:%s", typeName.CStr()));
89
90                 if (!reply2) {
91                         redisFree(m_Context);
92                         m_Context = NULL;
93                         return;
94                 }
95
96                 if (reply2->type == REDIS_REPLY_STATUS || reply2->type == REDIS_REPLY_ERROR) {
97                         Log(LogInformation, "RedisWriter")
98                             << "DEL icinga:status:" << typeName << ": " << reply2->str;
99                 }
100
101                 if (reply2->type == REDIS_REPLY_ERROR) {
102                         freeReplyObject(reply2);
103                         return;
104                 }
105
106                 freeReplyObject(reply2);
107
108                 /* fetch all objects and dump them */
109                 ConfigType *ctype = dynamic_cast<ConfigType *>(type.get());
110                 VERIFY(ctype);
111
112                 for (const ConfigObject::Ptr& object : ctype->GetObjects()) {
113                         SendConfigUpdate(object, typeName);
114                         SendStatusUpdate(object, typeName);
115                 }
116         }
117 }
118
119 void RedisWriter::SendConfigUpdate(const ConfigObject::Ptr& object, const String& typeName)
120 {
121         AssertOnWorkQueue();
122
123         /* Serialize config object attributes */
124         Dictionary::Ptr objectAttrs = SerializeObjectAttrs(object, FAConfig);
125
126         String jsonBody = JsonEncode(objectAttrs);
127
128         //TODO: checksum
129         String objectName = object->GetName();
130
131         redisReply *reply = reinterpret_cast<redisReply *>(redisCommand(m_Context, "HSET icinga:config:%s %s %s", typeName.CStr(), objectName.CStr(), jsonBody.CStr()));
132
133         if (!reply) {
134                 redisFree(m_Context);
135                 m_Context = NULL;
136                 return;
137         }
138
139         if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_ERROR) {
140                 Log(LogInformation, "RedisWriter")
141                     << "HSET icinga:config:" << typeName << " " << objectName << " " << jsonBody << ": " << reply->str;
142         }
143
144         if (reply->type == REDIS_REPLY_ERROR) {
145                 freeReplyObject(reply);
146                 return;
147         }
148
149         freeReplyObject(reply);
150 }
151
152 void RedisWriter::SendStatusUpdate(const ConfigObject::Ptr& object, const String& typeName)
153 {
154         AssertOnWorkQueue();
155
156         /* Serialize config object attributes */
157         Dictionary::Ptr objectAttrs = SerializeObjectAttrs(object, FAState);
158
159         String jsonBody = JsonEncode(objectAttrs);
160
161         //TODO: checksum
162         String objectName = object->GetName();
163
164         redisReply *reply = reinterpret_cast<redisReply *>(redisCommand(m_Context, "HSET icinga:status:%s %s %s", typeName.CStr(), objectName.CStr(), jsonBody.CStr()));
165
166         if (!reply) {
167                 redisFree(m_Context);
168                 m_Context = NULL;
169                 return;
170         }
171
172         if (reply->type == REDIS_REPLY_STATUS || reply->type == REDIS_REPLY_ERROR) {
173                 Log(LogInformation, "RedisWriter")
174                     << "HSET icinga:status:" << typeName << " " << objectName << " " << jsonBody << ": " << reply->str;
175         }
176
177         if (reply->type == REDIS_REPLY_ERROR) {
178                 freeReplyObject(reply);
179                 return;
180         }
181
182         freeReplyObject(reply);
183 }
184
185 Dictionary::Ptr RedisWriter::SerializeObjectAttrs(const Object::Ptr& object, int fieldType)
186 {
187         Type::Ptr type = object->GetReflectionType();
188
189         std::vector<int> fids;
190
191         for (int fid = 0; fid < type->GetFieldCount(); fid++) {
192                 fids.push_back(fid);
193         }
194
195         Dictionary::Ptr resultAttrs = new Dictionary();
196
197         for (int& fid : fids) {
198                 Field field = type->GetFieldInfo(fid);
199
200                 if ((field.Attributes & fieldType) == 0)
201                         continue;
202
203                 Value val = object->GetField(fid);
204
205                 /* hide attributes which shouldn't be user-visible */
206                 if (field.Attributes & FANoUserView)
207                         continue;
208
209                 /* hide internal navigation fields */
210                 if (field.Attributes & FANavigation && !(field.Attributes & (FAConfig | FAState)))
211                         continue;
212
213                 Value sval = Serialize(val);
214                 resultAttrs->Set(field.Name, sval);
215         }
216
217         return resultAttrs;
218 }
219
220 void RedisWriter::StateChangedHandler(const ConfigObject::Ptr& object)
221 {
222         Type::Ptr type = object->GetReflectionType();
223
224         for (const RedisWriter::Ptr& rw : ConfigType::GetObjectsByType<RedisWriter>()) {
225                 rw->m_WorkQueue.Enqueue(boost::bind(&RedisWriter::SendStatusUpdate, rw.get(), object, type->GetName()));
226         }
227 }
228
229 void RedisWriter::VarsChangedHandler(const ConfigObject::Ptr& object)
230 {
231         Type::Ptr type = object->GetReflectionType();
232
233         for (const RedisWriter::Ptr& rw : ConfigType::GetObjectsByType<RedisWriter>()) {
234                 rw->m_WorkQueue.Enqueue(boost::bind(&RedisWriter::SendConfigUpdate, rw.get(), object, type->GetName()));
235         }
236 }
237
238 void RedisWriter::VersionChangedHandler(const ConfigObject::Ptr& object)
239 {
240         Type::Ptr type = object->GetReflectionType();
241
242         for (const RedisWriter::Ptr& rw : ConfigType::GetObjectsByType<RedisWriter>()) {
243                 rw->m_WorkQueue.Enqueue(boost::bind(&RedisWriter::SendConfigUpdate, rw.get(), object, type->GetName()));
244         }
245 }