Kea  1.5.0
cql_connection.cc
Go to the documentation of this file.
1 // Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
2 // Copyright (C) 2015-2018 Deutsche Telekom AG.
3 //
4 // Authors: Razvan Becheriu <razvan.becheriu@qualitance.com>
5 // Andrei Pavel <andrei.pavel@qualitance.com>
6 //
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
10 //
11 // http://www.apache.org/licenses/LICENSE-2.0
12 //
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
18 
19 #include <config.h>
20 
21 #include <cql/cql_connection.h>
22 #include <cql/cql_exchange.h>
23 #include <database/db_exceptions.h>
24 #include <database/db_log.h>
25 
26 #include <string>
27 
28 namespace isc {
29 namespace db {
30 
32  : DatabaseConnection(parameters), statements_(), cluster_(NULL),
33  session_(NULL), consistency_(CASS_CONSISTENCY_QUORUM), schema_meta_(NULL),
34  keyspace_meta_(NULL), force_consistency_(true) {
35 }
36 
38  // Free up the prepared statements, ignoring errors. Session and connection
39  // resources are deallocated.
40  CassError rc = CASS_OK;
41  std::string error;
42 
43  // Let's free the prepared statements.
44  for (StatementMapEntry s : statements_) {
45  CqlTaggedStatement statement = s.second;
46  if (statement.prepared_statement_) {
47  cass_prepared_free(statement.prepared_statement_);
48  }
49  }
50 
51  // If there's a session, tear it down and free the resources.
52  if (session_) {
53  cass_schema_meta_free(schema_meta_);
54  CassFuture* close_future = cass_session_close(session_);
55  cass_future_wait(close_future);
56  error = checkFutureError(
57  "CqlConnection::~CqlConnection(): cass_sesssion_close() != CASS_OK",
58  close_future);
59  rc = cass_future_error_code(close_future);
60  cass_future_free(close_future);
61  cass_session_free(session_);
62  session_ = NULL;
63  }
64 
65  // Free the cluster if there's one.
66  if (cluster_) {
67  cass_cluster_free(cluster_);
68  cluster_ = NULL;
69  }
70 
71  if (rc != CASS_OK) {
72  // We're closing the connection anyway. Let's not throw at this stage.
73  DB_LOG_ERROR(CQL_DEALLOC_ERROR).arg(error);
74  }
75 }
76 
77 void
79  CassError rc;
80  // Set up the values of the parameters
81  const char* contact_points = "127.0.0.1";
82  std::string scontact_points;
83  try {
84  scontact_points = getParameter("contact-points");
85  contact_points = scontact_points.c_str();
86  } catch (...) {
87  // No host. Fine, we'll use "127.0.0.1".
88  }
89 
90  const char* port = NULL;
91  std::string sport;
92  try {
93  sport = getParameter("port");
94  port = sport.c_str();
95  } catch (...) {
96  // No port. Fine, we'll use the default "9042".
97  }
98 
99  const char* user = NULL;
100  std::string suser;
101  try {
102  suser = getParameter("user");
103  user = suser.c_str();
104  } catch (...) {
105  // No user. Fine, we'll use NULL.
106  }
107 
108  const char* password = NULL;
109  std::string spassword;
110  try {
111  spassword = getParameter("password");
112  password = spassword.c_str();
113  } catch (...) {
114  // No password. Fine, we'll use NULL.
115  }
116 
117  const char* keyspace = "keatest";
118  std::string skeyspace;
119  try {
120  skeyspace = getParameter("keyspace");
121  keyspace = skeyspace.c_str();
122  } catch (...) {
123  // No keyspace name. Fine, we'll use "keatest".
124  }
125 
126  const char* reconnect_wait_time = NULL;
127  std::string sreconnect_wait_time;
128  try {
129  sreconnect_wait_time = getParameter("reconnect-wait-time");
130  reconnect_wait_time = sreconnect_wait_time.c_str();
131  } catch (...) {
132  // No reconnect wait time. Fine, we'll use the default "2000".
133  }
134 
135  const char* connect_timeout = NULL;
136  std::string sconnect_timeout;
137  try {
138  sconnect_timeout = getParameter("connect-timeout");
139  connect_timeout = sconnect_timeout.c_str();
140  } catch (...) {
141  // No connect timeout. Fine, we'll use the default "5000".
142  }
143 
144  const char* request_timeout = NULL;
145  std::string srequest_timeout;
146  try {
147  srequest_timeout = getParameter("request-timeout");
148  request_timeout = srequest_timeout.c_str();
149  } catch (...) {
150  // No request timeout. Fine, we'll use the default "12000".
151  }
152 
153  const char* tcp_keepalive = NULL;
154  std::string stcp_keepalive;
155  try {
156  stcp_keepalive = getParameter("tcp-keepalive");
157  tcp_keepalive = stcp_keepalive.c_str();
158  } catch (...) {
159  // No tcp-keepalive. Fine, we'll not use TCP keepalive.
160  }
161 
162  std::string stcp_nodelay;
163  try {
164  stcp_nodelay = getParameter("tcp-nodelay");
165  } catch (...) {
166  // No tcp-nodelay. Fine, we'll use the default false.
167  }
168 
169  cluster_ = cass_cluster_new();
170  cass_cluster_set_contact_points(cluster_, contact_points);
171 
172  if (user && password) {
173  cass_cluster_set_credentials(cluster_, user, password);
174  }
175 
176  if (port) {
177  int32_t port_number;
178  try {
179  port_number = boost::lexical_cast<int32_t>(port);
180  if (port_number < 1 || port_number > 65535) {
182  "CqlConnection::openDatabase(): "
183  "port outside of range, expected "
184  "1-65535, instead got "
185  << port);
186  }
187  } catch (const boost::bad_lexical_cast& ex) {
189  "CqlConnection::openDatabase(): invalid "
190  "port, expected castable to int, instead got "
191  "\"" << port
192  << "\", " << ex.what());
193  }
194  cass_cluster_set_port(cluster_, port_number);
195  }
196 
197  if (reconnect_wait_time) {
198  int32_t reconnect_wait_time_number;
199  try {
200  reconnect_wait_time_number =
201  boost::lexical_cast<int32_t>(reconnect_wait_time);
202  if (reconnect_wait_time_number < 0) {
204  "CqlConnection::openDatabase(): invalid reconnect "
205  "wait time, expected positive number, instead got "
206  << reconnect_wait_time);
207  }
208  } catch (const boost::bad_lexical_cast& ex) {
210  "CqlConnection::openDatabase(): "
211  "invalid reconnect wait time, expected "
212  "castable to int, instead got \""
213  << reconnect_wait_time << "\", " << ex.what());
214  }
215  cass_cluster_set_reconnect_wait_time(cluster_,
216  reconnect_wait_time_number);
217  }
218 
219  if (connect_timeout) {
220  int32_t connect_timeout_number;
221  try {
222  connect_timeout_number =
223  boost::lexical_cast<int32_t>(connect_timeout);
224  if (connect_timeout_number < 0) {
226  "CqlConnection::openDatabase(): "
227  "invalid connect timeout, expected "
228  "positive number, instead got "
229  << connect_timeout);
230  }
231  } catch (const boost::bad_lexical_cast& ex) {
233  "CqlConnection::openDatabase(): invalid connect timeout, "
234  "expected castable to int, instead got \""
235  << connect_timeout << "\", " << ex.what());
236  }
237  cass_cluster_set_connect_timeout(cluster_, connect_timeout_number);
238  }
239 
240  if (request_timeout) {
241  int32_t request_timeout_number;
242  try {
243  request_timeout_number =
244  boost::lexical_cast<int32_t>(request_timeout);
245  if (request_timeout_number < 0) {
247  "CqlConnection::openDatabase(): "
248  "invalid request timeout, expected "
249  "positive number, instead got "
250  << request_timeout);
251  }
252  } catch (const boost::bad_lexical_cast& ex) {
254  "CqlConnection::openDatabase(): invalid request timeout, "
255  "expected castable to int, instead got \""
256  << request_timeout << "\", " << ex.what());
257  }
258  cass_cluster_set_request_timeout(cluster_, request_timeout_number);
259  }
260 
261  if (tcp_keepalive) {
262  int32_t tcp_keepalive_number;
263  try {
264  tcp_keepalive_number = boost::lexical_cast<int32_t>(tcp_keepalive);
265  if (tcp_keepalive_number < 0) {
267  "CqlConnection::openDatabase(): "
268  "invalid TCP keepalive, expected "
269  "positive number, instead got "
270  << tcp_keepalive);
271  }
272  } catch (const boost::bad_lexical_cast& ex) {
274  "CqlConnection::openDatabase(): invalid TCP keepalive, "
275  "expected castable to int, instead got \""
276  << tcp_keepalive << "\", " << ex.what());
277  }
278  cass_cluster_set_tcp_keepalive(cluster_, cass_true,
279  tcp_keepalive_number);
280  }
281 
282  if (stcp_nodelay == "true") {
283  cass_cluster_set_tcp_nodelay(cluster_, cass_true);
284  }
285 
286  session_ = cass_session_new();
287 
288  CassFuture* connect_future =
289  cass_session_connect_keyspace(session_, cluster_, keyspace);
290  cass_future_wait(connect_future);
291  const std::string error =
292  checkFutureError("CqlConnection::openDatabase(): "
293  "cass_session_connect_keyspace() != CASS_OK",
294  connect_future);
295  rc = cass_future_error_code(connect_future);
296  cass_future_free(connect_future);
297  if (rc != CASS_OK) {
298  cass_session_free(session_);
299  session_ = NULL;
300  cass_cluster_free(cluster_);
301  cluster_ = NULL;
302  isc_throw(DbOpenError, error);
303  }
304 
305  // Get keyspace meta.
306  schema_meta_ = cass_session_get_schema_meta(session_);
307  keyspace_meta_ = cass_schema_meta_keyspace_by_name(schema_meta_, keyspace);
308  if (!keyspace_meta_) {
309  isc_throw(DbOpenError, "CqlConnection::openDatabase(): "
310  "!cass_schema_meta_keyspace_by_name()");
311  }
312 }
313 
314 void
316  CassError rc = CASS_OK;
317  for (StatementMapEntry it : statements) {
318  CqlTaggedStatement& tagged_statement = it.second;
319  if (statements_.find(tagged_statement.name_) != statements_.end()) {
321  "CqlConnection::prepareStatements(): "
322  "duplicate statement with name "
323  << tagged_statement.name_);
324  }
325 
326  CassFuture* future =
327  cass_session_prepare(session_, tagged_statement.text_);
328  cass_future_wait(future);
329  const std::string error =
330  checkFutureError("CqlConnection::prepareStatements():"
331  " cass_session_prepare() != CASS_OK",
332  future, tagged_statement.name_);
333  rc = cass_future_error_code(future);
334  if (rc != CASS_OK) {
335  cass_future_free(future);
336  isc_throw(DbOperationError, error);
337  }
338 
339  tagged_statement.prepared_statement_ = cass_future_get_prepared(future);
340  statements_.insert(it);
341  cass_future_free(future);
342  }
343 }
344 
345 void
346 CqlConnection::setConsistency(bool force, CassConsistency consistency) {
347  force_consistency_ = force;
348  consistency_ = consistency;
349 }
350 
351 void
354 }
355 
356 void
359 }
360 
361 void
364 }
365 
366 const std::string
367 CqlConnection::checkFutureError(const std::string& what,
368  CassFuture* future,
369  StatementTag statement_tag /* = NULL */) {
370  CassError cass_error = cass_future_error_code(future);
371  const char* error_message;
372  size_t error_message_size;
373  cass_future_error_message(future, &error_message, &error_message_size);
374 
375  std::stringstream stream;
376  if (statement_tag && std::strlen(statement_tag) > 0) {
377  // future is from cass_session_execute() call.
378  stream << "Statement ";
379  stream << statement_tag;
380  } else {
381  // future is from cass_session_*() call.
382  stream << "Session action ";
383  }
384  if (cass_error == CASS_OK) {
385  stream << " executed succesfully.";
386  } else {
387  stream << " failed, Kea error: " << what
388  << ", Cassandra error code: " << cass_error_desc(cass_error)
389  << ", Cassandra future error: " << error_message;
390  }
391  return stream.str();
392 }
393 
394 } // namespace dhcp
395 } // namespace isc
We want to reuse the database backend connection and exchange code for other uses,...
StatementMap statements_
Pointer to external array of tagged statements containing statement name, array of names of bind para...
void setConsistency(bool force, CassConsistency consistency)
Set consistency.
StatementTag name_
Short description of the query.
virtual void commit()
Commit Transactions.
const CassPrepared * prepared_statement_
Internal Cassandra object representing the prepared statement.
#define DB_LOG_DEBUG(LEVEL, MESSAGE)
Macros.
Definition: db_log.h:115
bool force_consistency_
CQL consistency enabled.
void startTransaction()
Start transaction.
void prepareStatements(StatementMap &statements)
Prepare statements.
void openDatabase()
Open database.
virtual void rollback()
Rollback Transactions.
Common database connection class.
virtual ~CqlConnection()
Destructor.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Exception thrown on failure to open database.
char const *const StatementTag
Statement index representing the statement name.
const CassKeyspaceMeta * keyspace_meta_
Keyspace meta information, used for UDTs.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Defines a single statement or query.
CassConsistency consistency_
CQL consistency.
CassSession * session_
CQL session handle.
std::string getParameter(const std::string &name) const
Returns value of a connection parameter.
CassCluster * cluster_
CQL connection handle.
const int DB_DBG_TRACE_DETAIL
Database logging levels.
Definition: db_log.h:40
Defines the logger used by the top-level component of kea-dhcp-ddns.
std::unordered_map< StatementTag, CqlTaggedStatement, StatementTagHash, StatementTagEqual > StatementMap
A container for all statements.
const CassSchemaMeta * schema_meta_
#define DB_LOG_ERROR(MESSAGE)
Definition: db_log.h:137
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
static const std::string checkFutureError(const std::string &what, CassFuture *future, StatementTag statement_tag=NULL)
Check for errors.
std::pair< StatementTag, CqlTaggedStatement > StatementMapEntry
A type for a single entry on the statements map.
Exception thrown on failure to execute a database function.
CqlConnection(const ParameterMap &parameters)
Constructor.
char const *const text_
Text representation of the actual query.