Fawkes API  Fawkes Development Version
mongodb_thread.cpp
1 
2 /***************************************************************************
3  * mongodb_thread.cpp - MongoDB Thread
4  *
5  * Created: Sun Dec 05 23:32:13 2010
6  * Copyright 2006-2015 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include "mongodb_thread.h"
23 
24 #ifdef HAVE_MONGODB_VERSION_H
25 # include <mongo/client/init.h>
26 #endif
27 
28 using namespace mongo;
29 using namespace fawkes;
30 
31 /** Client configuration. */
33 {
34  public:
35  /** Connection mode enumeration. */
36  typedef enum {
37  CONNECTION, /**< connect to single node */
38  REPLICA_SET, /**< connect to replica set */
40 
42  std::string cfgname, std::string prefix);
43  mongo::DBClientBase * create_client();
44 
45  /** Check if configuration is active.
46  * @return true if configuration is active, false otherwise
47  */
48  bool is_active() const { return __active; }
49 
50  void log(Logger *logger, const char *component, const char *indent);
51 
52  private:
53  void read_authinfo(Configuration *config, Logger *logger,
54  std::string cfgname, std::string prefix);
55 
56  private:
57  std::string __logcomp;
58  bool __active;
59  ConnectionMode __mode;
60  mongo::HostAndPort __conn_hostport;
61  std::vector<mongo::HostAndPort> __replicaset_hostports;
62 
63  /// @cond INTERNALS
64  typedef struct _AuthInfo {
65  _AuthInfo(std::string dbname, std::string username, std::string clearpwd)
66  { this->dbname = dbname; this->username = username;
67  this->clearpwd = clearpwd; }
68  std::string dbname;
69  std::string username;
70  std::string clearpwd;
71  } AuthInfo;
72  /// @endcond
73 
74  std::list<AuthInfo> __auth_infos;
75 };
76 
77 
78 /** @class MongoDBThread "mongodb_thread.h"
79  * MongoDB Thread.
80  * This thread maintains an active connection to MongoDB and provides an
81  * aspect to access MongoDB to make it convenient for other threads to use
82  * MongoDB.
83  *
84  * @author Tim Niemueller
85  */
86 
87 /** Constructor. */
89  : Thread("MongoDBThread", Thread::OPMODE_WAITFORWAKEUP),
90  AspectProviderAspect(&__mongodb_aspect_inifin),
91  __mongodb_aspect_inifin(this)
92 {
93 }
94 
95 
96 /** Destructor. */
98 {
99 }
100 
101 
102 void
104 {
105 #ifdef HAVE_MONGODB_VERSION_H
106  mongo::client::initialize();
107 #endif
108 
109  std::set<std::string> ignored_configs;
110 
111  std::string prefix = "/plugins/mongodb/clients/";
112 
113 #if __cplusplus >= 201103L || (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 4)
114  std::unique_ptr<Configuration::ValueIterator> i(config->search(prefix.c_str()));
115 #else
116  std::auto_ptr<Configuration::ValueIterator> i(config->search(prefix.c_str()));
117 #endif
118  while (i->next()) {
119  std::string cfg_name = std::string(i->path()).substr(prefix.length());
120  cfg_name = cfg_name.substr(0, cfg_name.find("/"));
121 
122  if ( (__configs.find(cfg_name) == __configs.end()) &&
123  (ignored_configs.find(cfg_name) == ignored_configs.end()) ) {
124 
125  std::string cfg_prefix = prefix + cfg_name + "/";
126 
127  try {
128  ClientConf *conf = new ClientConf(config, logger, cfg_name, cfg_prefix);
129  if (conf->is_active()) {
130  __configs[cfg_name] = conf;
131  logger->log_info(name(), "Added MongoDB client configuration %s",
132  cfg_name.c_str());
133  conf->log(logger, name(), " ");
134  } else {
135  logger->log_info(name(), "Ignoring disabled MongoDB client "
136  "configuration %s", cfg_name.c_str());
137  delete conf;
138  ignored_configs.insert(cfg_name);
139  }
140  } catch (Exception &e) {
141  logger->log_warn(name(), "Invalid MongoDB client config %s, ignoring, "
142  "exception follows.", cfg_name.c_str());
143  ignored_configs.insert(cfg_name);
144  }
145  }
146  }
147 
148  if (__configs.empty()) {
149  throw Exception("No active MongoDB configurations found");
150  }
151 }
152 
153 
154 void
156 {
157  std::map<std::string, ClientConf *>::iterator i;
158  for (i = __configs.begin(); i != __configs.end(); ++i) {
159  delete i->second;
160  }
161  __configs.clear();
162 }
163 
164 
165 void
167 {
168 }
169 
170 mongo::DBClientBase *
171 MongoDBThread::create_client(const char *config_name)
172 {
173  const char *cname = config_name ? config_name : "default";
174 
175  if (__configs.find(cname) != __configs.end()) {
176  if (! __configs[cname]->is_active()) {
177  throw Exception("MongoDB config '%s' is not marked active", cname);
178  }
179  return __configs[cname]->create_client();
180  } else {
181  throw Exception("No MongoDB config named '%s' exists", cname);
182  }
183 }
184 
185 void
186 MongoDBThread::delete_client(mongo::DBClientBase *client)
187 {
188  delete client;
189 }
190 
191 
192 /** Read authentication info for given configuration.
193  * This will first try to read the fields auth_dbname, auth_username, and
194  * auth_password. If that fails, the auth/ subdirectory is crawled for subtrees
195  * that contain the just named entries.
196  * @param config configuration to query
197  * @param logger logger for info messages
198  * @param cfgname configuration name
199  * @param prefix configuration path prefix
200  */
201 void
202 MongoDBThread::ClientConf::read_authinfo(Configuration *config, Logger *logger,
203  std::string cfgname, std::string prefix)
204 {
205  std::set<std::string> authinfos;
206 
207  try {
208  std::string dbname = config->get_string((prefix + "auth_dbname").c_str());
209  std::string username = config->get_string((prefix + "auth_username").c_str());
210  std::string password = config->get_string((prefix + "auth_password").c_str());
211  __auth_infos.push_back(AuthInfo(dbname, username, password));
212  } catch (Exception &e) {
213  logger->log_info(__logcomp.c_str(), "No default authentication info for "
214  "MongoDB client '%s'", cfgname.c_str());
215  }
216 
217 #if __cplusplus >= 201103L
218  std::unique_ptr<Configuration::ValueIterator>
219 #else
220  std::auto_ptr<Configuration::ValueIterator>
221 #endif
222  i(config->search((prefix + "auth/").c_str()));
223  while (i->next()) {
224  std::string auth_name = std::string(i->path()).substr(prefix.length());
225  auth_name = auth_name.substr(0, auth_name.find("/"));
226 
227  if (authinfos.find(auth_name) == authinfos.end()) {
228  try {
229  std::string ap = prefix + auth_name + "/";
230  std::string dbname = config->get_string((ap + "auth_dbname").c_str());
231  std::string username = config->get_string((ap + "auth_username").c_str());
232  std::string password = config->get_string((ap + "auth_password").c_str());
233  __auth_infos.push_back(AuthInfo(dbname, username, password));
234  } catch (Exception &e) {
235  logger->log_info(__logcomp.c_str(), "Incomplete extended auth info '%s' "
236  "for MongoDB client '%s'",
237  auth_name.c_str(), cfgname.c_str());
238  }
239  }
240  }
241 }
242 
243 /** Constructor.
244  * This will read the given configuration.
245  * @param config configuration to query
246  * @param logger logger for info messages
247  * @param cfgname configuration name
248  * @param prefix configuration path prefix
249  */
251  std::string cfgname, std::string prefix)
252 {
253  __logcomp = "MongoDB ClientConf " + cfgname;
254 
255  __active = false;
256  try {
257  __active = config->get_bool((prefix + "active").c_str());
258  } catch (Exception &e) {}
259 
260  std::string mode = "connection";
261  try {
262  mode = config->get_string((prefix + "mode").c_str());
263  } catch (Exception &e) {
264  logger->log_info(__logcomp.c_str(), "MongoDB config '%s' specifies no client "
265  "mode, assuming 'connection'.", cfgname.c_str());
266  }
267 
268  if (mode == "replica_set" || mode == "replicaset") {
269  __mode = REPLICA_SET;
270 
271 #if __cplusplus >= 201103L || (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 4)
272  std::unique_ptr<Configuration::ValueIterator>
273 #else
274  std::auto_ptr<Configuration::ValueIterator>
275 #endif
276  i(config->search((prefix + "hosts/").c_str()));
277  while (i->next()) {
278  if (i->is_string()) {
279  __replicaset_hostports.push_back(HostAndPort(i->get_string()));
280  }
281  }
282 
283  } else if (mode == "sync_cluster" || mode == "synccluster") {
284  throw Exception("sync_cluster connections are no longer supported");
285 
286  } else {
287  __mode = CONNECTION;
288 
289  __conn_hostport =
290  HostAndPort(config->get_string((prefix + "hostport").c_str()));
291  }
292 }
293 
294 /** Create MongoDB client for this configuration.
295  * @return MongoDB client
296  */
297 mongo::DBClientBase *
299 {
300  mongo::DBClientBase *client;
301  std::string errmsg;
302 
303  switch (__mode) {
304  case REPLICA_SET:
305  {
306  std::string noname = "";
307  DBClientReplicaSet *repset =
308  new DBClientReplicaSet(noname, __replicaset_hostports);
309  client = repset;
310  if (! repset->connect()) throw Exception("Cannot connect to database");
311  std::list<AuthInfo>::iterator ai;
312  for (ai = __auth_infos.begin(); ai != __auth_infos.end(); ++ai) {
313  if (!repset->auth(ai->dbname, ai->username, ai->clearpwd, errmsg, false)) {
314  throw Exception("Authenticating for %s as %s failed: %s",
315  ai->dbname.c_str(), ai->username.c_str(),
316  errmsg.c_str());
317  }
318  }
319  }
320  break;
321 
322  default:
323  {
324  DBClientConnection *clconn =
325  new DBClientConnection(/* auto reconnect */ true);
326  client = clconn;
327  std::string errmsg;
328  if (! clconn->connect(__conn_hostport, errmsg)) {
329  throw Exception("Could not connect to MongoDB at %s: %s",
330  __conn_hostport.toString().c_str(), errmsg.c_str());
331  }
332  std::list<AuthInfo>::iterator ai;
333  for (ai = __auth_infos.begin(); ai != __auth_infos.end(); ++ai) {
334  if (!clconn->auth(ai->dbname, ai->username, ai->clearpwd, errmsg, false)) {
335  throw Exception("Authenticating for %s as %s failed: %s",
336  ai->dbname.c_str(), ai->username.c_str(),
337  errmsg.c_str());
338  }
339  }
340  }
341  break;
342  }
343 
344  return client;
345 }
346 
347 
348 /** Write client configuration information to log.
349  * @param logger logger to write to
350  * @param component component to pass to logger
351  * @param indent indentation to put before each string
352  */
353 void
354 MongoDBThread::ClientConf::log(Logger *logger, const char *component,
355  const char *indent)
356 {
357  switch (__mode) {
358  case REPLICA_SET:
359  {
360  logger->log_info(component, "%smode: replica set", indent);
361  logger->log_info(component, "%shosts:", indent);
362  std::vector<mongo::HostAndPort>::iterator i;
363  for (i = __replicaset_hostports.begin();
364  i != __replicaset_hostports.end();
365  ++i)
366  {
367  logger->log_info(component, "%s - %s:", indent, i->toString().c_str());
368  }
369 
370  if (! __auth_infos.empty()) {
371  logger->log_info(component, "%sauth infos:", indent);
372  std::list<AuthInfo>::iterator a;
373  for (a = __auth_infos.begin(); a != __auth_infos.end(); ++a) {
374  logger->log_info(component, "%s - %s @ %s", indent, a->username.c_str(),
375  a->dbname.c_str());
376  }
377  }
378  }
379  break;
380 
381  default:
382  {
383  logger->log_info(component, "%smode: connection", indent);
384  logger->log_info(component, "%shost: %s", indent,
385  __conn_hostport.toString().c_str());
386  if (! __auth_infos.empty()) {
387  logger->log_info(component, "%sauth infos:", indent);
388  std::list<AuthInfo>::iterator a;
389  for (a = __auth_infos.begin(); a != __auth_infos.end(); ++a) {
390  logger->log_info(component, "%s - %s @ %s", indent, a->username.c_str(),
391  a->dbname.c_str());
392  }
393  }
394  }
395  break;
396  }
397 }
bool is_active() const
Check if configuration is active.
virtual void log_info(const char *component, const char *format,...)=0
Log informational message.
ClientConf(fawkes::Configuration *config, fawkes::Logger *logger, std::string cfgname, std::string prefix)
Constructor.
Fawkes library namespace.
Definition: mongodb.h:29
virtual bool get_bool(const char *path)=0
Get value from configuration which is of type bool.
virtual void finalize()
Finalize the thread.
virtual ValueIterator * search(const char *path)=0
Iterator with search results.
virtual bool next()=0
Check if there is another element and advance to this if possible.
Thread class encapsulation of pthreads.
Definition: thread.h:42
Client configuration.
Logger * logger
This is the Logger member used to access the logger.
Definition: logging.h:44
virtual ~MongoDBThread()
Destructor.
virtual void loop()
Code to execute in the thread.
ConnectionMode
Connection mode enumeration.
virtual bool is_string() const =0
Check if current value is a string.
Base class for exceptions in Fawkes.
Definition: exception.h:36
virtual std::string get_string() const =0
Get string value.
const char * name() const
Get name of thread.
Definition: thread.h:95
virtual void log_warn(const char *component, const char *format,...)=0
Log warning message.
virtual const char * path() const =0
Path of value.
Thread aspect provide a new aspect.
virtual mongo::DBClientBase * create_client(const char *config_name=0)
Create a new MongoDB client.
MongoDBThread()
Constructor.
virtual void delete_client(mongo::DBClientBase *client)
Delete a client.
Configuration * config
This is the Configuration member used to access the configuration.
Definition: configurable.h:44
Interface for configuration handling.
Definition: config.h:67
void log(Logger *logger, const char *component, const char *indent)
Write client configuration information to log.
mongo::DBClientBase * create_client()
Create MongoDB client for this configuration.
virtual void init()
Initialize the thread.
virtual std::string get_string(const char *path)=0
Get value from configuration which is of type string.
Interface for logging.
Definition: logger.h:34