Kea  1.5.0
mysql_host_data_source.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
10 #include <dhcp/libdhcp++.h>
11 #include <dhcp/option.h>
12 #include <dhcp/option_definition.h>
13 #include <dhcp/option_space.h>
14 #include <dhcpsrv/cfg_option.h>
15 #include <dhcpsrv/dhcpsrv_log.h>
17 #include <util/buffer.h>
18 #include <util/optional_value.h>
19 
20 #include <boost/algorithm/string/split.hpp>
21 #include <boost/algorithm/string/classification.hpp>
22 #include <boost/array.hpp>
23 #include <boost/pointer_cast.hpp>
24 #include <boost/static_assert.hpp>
25 
26 #include <mysql.h>
27 #include <mysqld_error.h>
28 
29 #include <stdint.h>
30 #include <string>
31 
32 using namespace isc;
33 using namespace isc::asiolink;
34 using namespace isc::db;
35 using namespace isc::dhcp;
36 using namespace isc::util;
37 using namespace isc::data;
38 using namespace std;
39 
40 namespace {
41 
46 const size_t ADDRESS6_TEXT_MAX_LEN = 39;
47 
50 const size_t CLIENT_CLASSES_MAX_LEN = 255;
51 
56 const size_t HOSTNAME_MAX_LEN = 255;
57 
59 const size_t OPTION_VALUE_MAX_LEN = 4096;
60 
62 const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
63 
65 const size_t OPTION_SPACE_MAX_LEN = 128;
66 
68 const size_t USER_CONTEXT_MAX_LEN = 8192;
69 
71 const size_t SERVER_HOSTNAME_MAX_LEN = 64;
72 
74 const size_t BOOT_FILE_NAME_MAX_LEN = 128;
75 
77 const size_t KEY_LEN = 16;
78 
83 const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
84 
113 class MySqlHostExchange {
114 private:
115 
117  static const size_t HOST_COLUMNS = 14;
118 
119 public:
120 
127  MySqlHostExchange(const size_t additional_columns_num = 0)
128  : columns_num_(HOST_COLUMNS + additional_columns_num),
129  bind_(columns_num_), columns_(columns_num_),
130  error_(columns_num_, MLM_FALSE), host_id_(0),
131  dhcp_identifier_length_(0), dhcp_identifier_type_(0),
132  dhcp4_subnet_id_(SUBNET_ID_UNUSED),
133  dhcp6_subnet_id_(SUBNET_ID_UNUSED), ipv4_address_(0),
134  hostname_length_(0), dhcp4_client_classes_length_(0),
135  dhcp6_client_classes_length_(0),
136  user_context_length_(0),
137  dhcp4_next_server_(0),
138  dhcp4_server_hostname_length_(0),
139  dhcp4_boot_file_name_length_(0),
140  dhcp4_subnet_id_null_(MLM_FALSE),
141  dhcp6_subnet_id_null_(MLM_FALSE),
142  ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
143  dhcp4_client_classes_null_(MLM_FALSE),
144  dhcp6_client_classes_null_(MLM_FALSE),
145  user_context_null_(MLM_FALSE),
146  dhcp4_next_server_null_(MLM_FALSE),
147  dhcp4_server_hostname_null_(MLM_FALSE),
148  dhcp4_boot_file_name_null_(MLM_FALSE),
149  auth_key_null_(MLM_FALSE) {
150 
151  // Fill arrays with 0 so as they don't include any garbage.
152  memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
153  memset(hostname_, 0, sizeof(hostname_));
154  memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
155  memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
156  memset(user_context_, 0, sizeof(user_context_));
157  memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
158  memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
159 
160  // Set the column names for use by this class. This only comprises
161  // names used by the MySqlHostExchange class. Derived classes will
162  // need to set names for the columns they use.
163  columns_[0] = "host_id";
164  columns_[1] = "dhcp_identifier";
165  columns_[2] = "dhcp_identifier_type";
166  columns_[3] = "dhcp4_subnet_id";
167  columns_[4] = "dhcp6_subnet_id";
168  columns_[5] = "ipv4_address";
169  columns_[6] = "hostname";
170  columns_[7] = "dhcp4_client_classes";
171  columns_[8] = "dhcp6_client_classes";
172  columns_[9] = "user_context";
173  columns_[10] = "dhcp4_next_server";
174  columns_[11] = "dhcp4_server_hostname";
175  columns_[12] = "dhcp4_boot_file_name";
176  columns_[13] = "auth_key";
177 
178  BOOST_STATIC_ASSERT(13 < HOST_COLUMNS);
179  };
180 
182  virtual ~MySqlHostExchange() {
183  }
184 
199  size_t findAvailColumn() const {
200  std::vector<std::string>::const_iterator empty_column =
201  std::find(columns_.begin(), columns_.end(), std::string());
202  return (std::distance(columns_.begin(), empty_column));
203  }
204 
208  uint64_t getHostId() const {
209  return (host_id_);
210  };
211 
222  static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
223  std::vector<my_bool>& error) {
224  for (size_t i = 0; i < error.size(); ++i) {
225  error[i] = MLM_FALSE;
226  bind[i].error = reinterpret_cast<char*>(&error[i]);
227  }
228  };
229 
243  static std::string getColumnsInError(std::vector<my_bool>& error,
244  const std::vector<std::string>& names) {
245  std::string result = "";
246 
247  // Accumulate list of column names
248  for (size_t i = 0; i < names.size(); ++i) {
249  if (error[i] == MLM_TRUE) {
250  if (!result.empty()) {
251  result += ", ";
252  }
253  result += names[i];
254  }
255  }
256 
257  if (result.empty()) {
258  result = "(None)";
259  }
260 
261  return (result);
262  };
263 
274  std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
275  // Store host object to ensure it remains valid.
276  host_ = host;
277 
278  // Initialize prior to constructing the array of MYSQL_BIND structures.
279  // It sets all fields, including is_null, to zero, so we need to set
280  // is_null only if it should be true. This gives up minor performance
281  // benefit while being safe approach.
282  memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
283 
284  // Set up the structures for the various components of the host structure.
285 
286  try {
287  // host_id : INT UNSIGNED NOT NULL
288  // The host_id is auto_incremented by MySQL database,
289  // so we need to pass the NULL value
290  host_id_ = 0;
291  bind_[0].buffer_type = MYSQL_TYPE_LONG;
292  bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
293  bind_[0].is_unsigned = MLM_TRUE;
294 
295  // dhcp_identifier : VARBINARY(128) NOT NULL
296  dhcp_identifier_length_ = host->getIdentifier().size();
297  memcpy(static_cast<void*>(dhcp_identifier_buffer_),
298  &(host->getIdentifier())[0],
299  host->getIdentifier().size());
300 
301  bind_[1].buffer_type = MYSQL_TYPE_BLOB;
302  bind_[1].buffer = dhcp_identifier_buffer_;
303  bind_[1].buffer_length = dhcp_identifier_length_;
304  bind_[1].length = &dhcp_identifier_length_;
305 
306  // dhcp_identifier_type : TINYINT NOT NULL
307  dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
308  bind_[2].buffer_type = MYSQL_TYPE_TINY;
309  bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
310  bind_[2].is_unsigned = MLM_TRUE;
311 
312  // dhcp4_subnet_id : INT UNSIGNED NULL
313  // Can't take an address of intermediate object, so let's store it
314  // in dhcp4_subnet_id_
315  dhcp4_subnet_id_ = host->getIPv4SubnetID();
316  dhcp4_subnet_id_null_ = host->getIPv4SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
317  bind_[3].buffer_type = MYSQL_TYPE_LONG;
318  bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
319  bind_[3].is_unsigned = MLM_TRUE;
320  bind_[3].is_null = &dhcp4_subnet_id_null_;
321 
322  // dhcp6_subnet_id : INT UNSIGNED NULL
323  // Can't take an address of intermediate object, so let's store it
324  // in dhcp6_subnet_id_
325  dhcp6_subnet_id_ = host->getIPv6SubnetID();
326  dhcp6_subnet_id_null_ = host->getIPv6SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
327  bind_[4].buffer_type = MYSQL_TYPE_LONG;
328  bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
329  bind_[4].is_unsigned = MLM_TRUE;
330  bind_[4].is_null = &dhcp6_subnet_id_null_;
331 
332  // ipv4_address : INT UNSIGNED NULL
333  // The address in the Host structure is an IOAddress object. Convert
334  // this to an integer for storage.
335  ipv4_address_ = host->getIPv4Reservation().toUint32();
336  ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
337  bind_[5].buffer_type = MYSQL_TYPE_LONG;
338  bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
339  bind_[5].is_unsigned = MLM_TRUE;
340  bind_[5].is_null = &ipv4_address_null_;
341 
342  // hostname : VARCHAR(255) NULL
343  strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
344  hostname_length_ = host->getHostname().length();
345  bind_[6].buffer_type = MYSQL_TYPE_STRING;
346  bind_[6].buffer = reinterpret_cast<char*>(hostname_);
347  bind_[6].buffer_length = hostname_length_;
348 
349  // dhcp4_client_classes : VARCHAR(255) NULL
350  bind_[7].buffer_type = MYSQL_TYPE_STRING;
351  // Override default separator to not include space after comma.
352  string classes4_txt = host->getClientClasses4().toText(",");
353  strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
354  bind_[7].buffer = dhcp4_client_classes_;
355  bind_[7].buffer_length = classes4_txt.length();
356 
357  // dhcp6_client_classes : VARCHAR(255) NULL
358  bind_[8].buffer_type = MYSQL_TYPE_STRING;
359  // Override default separator to not include space after comma.
360  string classes6_txt = host->getClientClasses6().toText(",");
361  strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
362  bind_[8].buffer = dhcp6_client_classes_;
363  bind_[8].buffer_length = classes6_txt.length();
364 
365  // user_context : TEXT NULL
366  ConstElementPtr ctx = host->getContext();
367  if (ctx) {
368  bind_[9].buffer_type = MYSQL_TYPE_STRING;
369  string ctx_txt = ctx->str();
370  strncpy(user_context_, ctx_txt.c_str(), USER_CONTEXT_MAX_LEN - 1);
371  bind_[9].buffer = user_context_;
372  bind_[9].buffer_length = ctx_txt.length();
373  } else {
374  bind_[9].buffer_type = MYSQL_TYPE_NULL;
375  }
376 
377  // ipv4_address : INT UNSIGNED NULL
378  // The address in the Host structure is an IOAddress object. Convert
379  // this to an integer for storage.
380  dhcp4_next_server_ = host->getNextServer().toUint32();
381  bind_[10].buffer_type = MYSQL_TYPE_LONG;
382  bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
383  bind_[10].is_unsigned = MLM_TRUE;
384  // bind_[10].is_null = &MLM_FALSE; // commented out for performance
385  // reasons, see memset() above
386 
387  // dhcp4_server_hostname
388  bind_[11].buffer_type = MYSQL_TYPE_STRING;
389  std::string server_hostname = host->getServerHostname();
390  strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
391  SERVER_HOSTNAME_MAX_LEN - 1);
392  bind_[11].buffer = dhcp4_server_hostname_;
393  bind_[11].buffer_length = server_hostname.length();
394 
395  // dhcp4_boot_file_name
396  bind_[12].buffer_type = MYSQL_TYPE_STRING;
397  std::string boot_file_name = host->getBootFileName();
398  strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
399  BOOT_FILE_NAME_MAX_LEN - 1);
400  bind_[12].buffer = dhcp4_boot_file_name_;
401  bind_[12].buffer_length = boot_file_name.length();
402 
403  // auth key
404  bind_[13].buffer_type = MYSQL_TYPE_STRING;
405  std::string auth_key = host->getKey().ToText();
406  std::strncpy(auth_key_, auth_key.c_str(), KEY_LEN);
407  auth_key_null_ = auth_key.empty() ? MLM_TRUE : MLM_FALSE;
408  bind_[13].buffer = auth_key_;
409  bind_[13].buffer_length = auth_key.length();
410 
411  } catch (const std::exception& ex) {
413  "Could not create bind array from Host: "
414  << host->getHostname() << ", reason: " << ex.what());
415  }
416 
417  // Add the data to the vector. Note the end element is one after the
418  // end of the array.
419  return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[columns_num_]));
420  };
421 
429  virtual std::vector<MYSQL_BIND> createBindForReceive() {
430  // Initialize MYSQL_BIND array.
431  // It sets all fields, including is_null, to zero, so we need to set
432  // is_null only if it should be true. This gives up minor performance
433  // benefit while being safe approach. For improved readability, the
434  // code that explicitly sets is_null is there, but is commented out.
435  // This also takes care of setting bind_[X].is_null to MLM_FALSE.
436  memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
437 
438  // host_id : INT UNSIGNED NOT NULL
439  bind_[0].buffer_type = MYSQL_TYPE_LONG;
440  bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
441  bind_[0].is_unsigned = MLM_TRUE;
442 
443  // dhcp_identifier : VARBINARY(128) NOT NULL
444  dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
445  bind_[1].buffer_type = MYSQL_TYPE_BLOB;
446  bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
447  bind_[1].buffer_length = dhcp_identifier_length_;
448  bind_[1].length = &dhcp_identifier_length_;
449 
450  // dhcp_identifier_type : TINYINT NOT NULL
451  bind_[2].buffer_type = MYSQL_TYPE_TINY;
452  bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
453  bind_[2].is_unsigned = MLM_TRUE;
454 
455  // dhcp4_subnet_id : INT UNSIGNED NULL
456  dhcp4_subnet_id_null_ = MLM_FALSE;
457  bind_[3].buffer_type = MYSQL_TYPE_LONG;
458  bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
459  bind_[3].is_unsigned = MLM_TRUE;
460  bind_[3].is_null = &dhcp4_subnet_id_null_;
461 
462  // dhcp6_subnet_id : INT UNSIGNED NULL
463  dhcp6_subnet_id_null_ = MLM_FALSE;
464  bind_[4].buffer_type = MYSQL_TYPE_LONG;
465  bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
466  bind_[4].is_unsigned = MLM_TRUE;
467  bind_[4].is_null = &dhcp6_subnet_id_null_;
468 
469  // ipv4_address : INT UNSIGNED NULL
470  ipv4_address_null_ = MLM_FALSE;
471  bind_[5].buffer_type = MYSQL_TYPE_LONG;
472  bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
473  bind_[5].is_unsigned = MLM_TRUE;
474  bind_[5].is_null = &ipv4_address_null_;
475 
476  // hostname : VARCHAR(255) NULL
477  hostname_null_ = MLM_FALSE;
478  hostname_length_ = sizeof(hostname_);
479  bind_[6].buffer_type = MYSQL_TYPE_STRING;
480  bind_[6].buffer = reinterpret_cast<char*>(hostname_);
481  bind_[6].buffer_length = hostname_length_;
482  bind_[6].length = &hostname_length_;
483  bind_[6].is_null = &hostname_null_;
484 
485  // dhcp4_client_classes : VARCHAR(255) NULL
486  dhcp4_client_classes_null_ = MLM_FALSE;
487  dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
488  bind_[7].buffer_type = MYSQL_TYPE_STRING;
489  bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
490  bind_[7].buffer_length = dhcp4_client_classes_length_;
491  bind_[7].length = &dhcp4_client_classes_length_;
492  bind_[7].is_null = &dhcp4_client_classes_null_;
493 
494  // dhcp6_client_classes : VARCHAR(255) NULL
495  dhcp6_client_classes_null_ = MLM_FALSE;
496  dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
497  bind_[8].buffer_type = MYSQL_TYPE_STRING;
498  bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
499  bind_[8].buffer_length = dhcp6_client_classes_length_;
500  bind_[8].length = &dhcp6_client_classes_length_;
501  bind_[8].is_null = &dhcp6_client_classes_null_;
502 
503  // user_context : TEXT NULL
504  user_context_null_ = MLM_FALSE;
505  user_context_length_ = sizeof(user_context_);
506  bind_[9].buffer_type = MYSQL_TYPE_STRING;
507  bind_[9].buffer = reinterpret_cast<char*>(user_context_);
508  bind_[9].buffer_length = user_context_length_;
509  bind_[9].length = &user_context_length_;
510  bind_[9].is_null = &user_context_null_;
511 
512  // dhcp4_next_server
513  dhcp4_next_server_null_ = MLM_FALSE;
514  bind_[10].buffer_type = MYSQL_TYPE_LONG;
515  bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
516  bind_[10].is_unsigned = MLM_TRUE;
517  bind_[10].is_null = &dhcp4_next_server_null_;
518 
519  // dhcp4_server_hostname
520  dhcp4_server_hostname_null_ = MLM_FALSE;
521  dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
522  bind_[11].buffer_type = MYSQL_TYPE_STRING;
523  bind_[11].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
524  bind_[11].buffer_length = dhcp4_server_hostname_length_;
525  bind_[11].length = &dhcp4_server_hostname_length_;
526  bind_[11].is_null = &dhcp4_server_hostname_null_;
527 
528  // dhcp4_boot_file_name
529  dhcp4_boot_file_name_null_ = MLM_FALSE;
530  dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
531  bind_[12].buffer_type = MYSQL_TYPE_STRING;
532  bind_[12].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
533  bind_[12].buffer_length = dhcp4_boot_file_name_length_;
534  bind_[12].length = &dhcp4_boot_file_name_length_;
535  bind_[12].is_null = &dhcp4_boot_file_name_null_;
536 
537  // auth_key_
538  auth_key_null_ = MLM_FALSE;
539  auth_key_length_ = sizeof(auth_key_);
540  bind_[13].buffer_type = MYSQL_TYPE_STRING;
541  bind_[13].buffer = reinterpret_cast<char*>(auth_key_);
542  bind_[13].buffer_length = auth_key_length_;
543  bind_[13].length = &auth_key_length_;
544  bind_[13].is_null = &auth_key_null_;
545 
546  // Add the error flags
547  setErrorIndicators(bind_, error_);
548 
549  // Add the data to the vector. Note the end element is one after the
550  // end of the array.
551  return (bind_);
552  };
553 
562  HostPtr retrieveHost() {
563  // Check if the identifier stored in the database is correct.
564  if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
565  isc_throw(BadValue, "invalid dhcp identifier type returned: "
566  << static_cast<int>(dhcp_identifier_type_));
567  }
568  // Set the dhcp identifier type in a variable of the appropriate
569  // data type.
570  Host::IdentifierType type =
571  static_cast<Host::IdentifierType>(dhcp_identifier_type_);
572 
573  // Set DHCPv4 subnet ID to the value returned. If NULL returned,
574  // set to 0.
575  SubnetID ipv4_subnet_id(SUBNET_ID_UNUSED);
576  if (dhcp4_subnet_id_null_ == MLM_FALSE) {
577  ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
578  }
579 
580  // Set DHCPv6 subnet ID to the value returned. If NULL returned,
581  // set to 0.
582  SubnetID ipv6_subnet_id(SUBNET_ID_UNUSED);
583  if (dhcp6_subnet_id_null_ == MLM_FALSE) {
584  ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
585  }
586 
587  // Set IPv4 address reservation if it was given, if not, set IPv4 zero
588  // address
589  asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
590  if (ipv4_address_null_ == MLM_FALSE) {
591  ipv4_reservation = asiolink::IOAddress(ipv4_address_);
592  }
593 
594  // Set hostname if non NULL value returned. Otherwise, leave an
595  // empty string.
596  std::string hostname;
597  if (hostname_null_ == MLM_FALSE) {
598  hostname = std::string(hostname_, hostname_length_);
599  }
600 
601  // Set DHCPv4 client classes if non NULL value returned.
602  std::string dhcp4_client_classes;
603  if (dhcp4_client_classes_null_ == MLM_FALSE) {
604  dhcp4_client_classes = std::string(dhcp4_client_classes_,
605  dhcp4_client_classes_length_);
606  }
607 
608  // Set DHCPv6 client classes if non NULL value returned.
609  std::string dhcp6_client_classes;
610  if (dhcp6_client_classes_null_ == MLM_FALSE) {
611  dhcp6_client_classes = std::string(dhcp6_client_classes_,
612  dhcp6_client_classes_length_);
613  }
614 
615  // Convert user_context to string as well.
616  std::string user_context;
617  if (user_context_null_ == MLM_FALSE) {
618  user_context_[user_context_length_] = '\0';
619  user_context.assign(user_context_);
620  }
621 
622  // Set next server value (siaddr) if non NULL value returned.
623  asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
624  if (dhcp4_next_server_null_ == MLM_FALSE) {
625  next_server = asiolink::IOAddress(dhcp4_next_server_);
626  }
627 
628  // Set server hostname (sname) if non NULL value returned.
629  std::string dhcp4_server_hostname;
630  if (dhcp4_server_hostname_null_ == MLM_FALSE) {
631  dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
632  dhcp4_server_hostname_length_);
633  }
634 
635  // Set boot file name (file) if non NULL value returned.
636  std::string dhcp4_boot_file_name;
637  if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
638  dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
639  dhcp4_boot_file_name_length_);
640  }
641 
642  // Set the auth key if a non empty array is retrieved
643  std::string auth_key;
644  if (auth_key_null_ == MLM_FALSE) {
645  auth_key = std::string(auth_key_, auth_key_length_);
646  }
647 
648  // Create and return Host object from the data gathered.
649  HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
650  type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
651  hostname, dhcp4_client_classes, dhcp6_client_classes,
652  next_server, dhcp4_server_hostname,
653  dhcp4_boot_file_name, AuthKey(auth_key)));
654  h->setHostId(host_id_);
655 
656  // Set the user context if there is one.
657  if (!user_context.empty()) {
658  try {
659  ConstElementPtr ctx = Element::fromJSON(user_context);
660  if (!ctx || (ctx->getType() != Element::map)) {
661  isc_throw(BadValue, "user context '" << user_context
662  << "' is not a JSON map");
663  }
664  h->setContext(ctx);
665  } catch (const isc::data::JSONError& ex) {
666  isc_throw(BadValue, "user context '" << user_context
667  << "' is invalid JSON: " << ex.what());
668  }
669  }
670 
671  return (h);
672  };
673 
687  virtual void processFetchedData(ConstHostCollection& hosts) {
688  HostPtr host;
689  // Add new host only if there are no hosts yet or the host id of the
690  // most recently added host is different than the host id of the
691  // currently processed host.
692  if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
693  // Create Host object from the fetched data and append it to the
694  // collection.
695  host = retrieveHost();
696  hosts.push_back(host);
697  }
698  }
699 
710  std::string getErrorColumns() {
711  return (getColumnsInError(error_, columns_));
712  };
713 
714 protected:
715 
717  size_t columns_num_;
718 
720  std::vector<MYSQL_BIND> bind_;
721 
723  std::vector<std::string> columns_;
724 
726  std::vector<my_bool> error_;
727 
730  HostPtr host_;
731 
732 private:
733 
735  uint64_t host_id_;
736 
739  uint8_t dhcp_identifier_buffer_[DUID::MAX_DUID_LEN];
740 
742  unsigned long dhcp_identifier_length_;
743 
746  uint8_t dhcp_identifier_type_;
747 
749  uint32_t dhcp4_subnet_id_;
750 
752  uint32_t dhcp6_subnet_id_;
753 
755  uint32_t ipv4_address_;
756 
758  char hostname_[HOSTNAME_MAX_LEN];
759 
761  unsigned long hostname_length_;
762 
764  char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];
765 
768  unsigned long dhcp4_client_classes_length_;
769 
771  char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];
772 
775  unsigned long dhcp6_client_classes_length_;
776 
778  char user_context_[USER_CONTEXT_MAX_LEN];
779 
781  unsigned long user_context_length_;
782 
784  uint32_t dhcp4_next_server_;
785 
787  char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];
788 
790  unsigned long dhcp4_server_hostname_length_;
791 
793  char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];
794 
796  unsigned long dhcp4_boot_file_name_length_;
797 
799  char auth_key_[KEY_LEN];
800 
802  unsigned long auth_key_length_;
803 
806 
807  my_bool dhcp4_subnet_id_null_;
809 
811  my_bool dhcp6_subnet_id_null_;
812 
814  my_bool ipv4_address_null_;
815 
817  my_bool hostname_null_;
818 
821  my_bool dhcp4_client_classes_null_;
822 
825  my_bool dhcp6_client_classes_null_;
826 
828  my_bool user_context_null_;
829 
831  my_bool dhcp4_next_server_null_;
832 
834  my_bool dhcp4_server_hostname_null_;
835 
837  my_bool dhcp4_boot_file_name_null_;
838 
840  my_bool auth_key_null_;
841 
843 };
844 
854 class MySqlHostWithOptionsExchange : public MySqlHostExchange {
855 private:
856 
858  static const size_t OPTION_COLUMNS = 7;
859 
874  class OptionProcessor {
875  public:
876 
883  OptionProcessor(const Option::Universe& universe,
884  const size_t start_column)
885  : universe_(universe), start_column_(start_column), option_id_(0),
886  code_(0), value_length_(0), formatted_value_length_(0),
887  space_length_(0), persistent_(false), user_context_length_(0),
888  option_id_null_(MLM_FALSE), code_null_(MLM_FALSE),
889  value_null_(MLM_FALSE), formatted_value_null_(MLM_FALSE),
890  space_null_(MLM_FALSE), user_context_null_(MLM_FALSE),
891  option_id_index_(start_column), code_index_(start_column_ + 1),
892  value_index_(start_column_ + 2),
893  formatted_value_index_(start_column_ + 3),
894  space_index_(start_column_ + 4),
895  persistent_index_(start_column_ + 5),
896  user_context_index_(start_column_ + 6),
897  most_recent_option_id_(0) {
898 
899  memset(value_, 0, sizeof(value_));
900  memset(formatted_value_, 0, sizeof(formatted_value_));
901  memset(space_, 0, sizeof(space_));
902  memset(user_context_, 0, sizeof(user_context_));
903  }
904 
906  uint64_t getOptionId() const {
907  if (option_id_null_ == MLM_FALSE) {
908  return (option_id_);
909  }
910  return (0);
911  }
912 
925  void retrieveOption(const CfgOptionPtr& cfg) {
926  // option_id may be NULL if dhcp4_options or dhcp6_options table
927  // doesn't contain any options for the particular host. Also, the
928  // current option id must be greater than id if the most recent
929  // option because options are ordered by option id. Otherwise
930  // we assume that this is already processed option.
931  if ((option_id_null_ == MLM_TRUE) ||
932  (most_recent_option_id_ >= option_id_)) {
933  return;
934  }
935 
936  // Remember current option id as the most recent processed one. We
937  // will be comparing it with option ids in subsequent rows.
938  most_recent_option_id_ = option_id_;
939 
940  // Convert it to string object for easier comparison.
941  std::string space;
942  if (space_null_ == MLM_FALSE) {
943  // Typically, the string values returned by the database are not
944  // NULL terminated.
945  space_[space_length_] = '\0';
946  space.assign(space_);
947  }
948 
949  // If empty or null space provided, use a default top level space.
950  if (space.empty()) {
951  space = (universe_ == Option::V4 ? "dhcp4" : "dhcp6");
952  }
953 
954  // Convert formatted_value to string as well.
955  std::string formatted_value;
956  if (formatted_value_null_ == MLM_FALSE) {
957  formatted_value_[formatted_value_length_] = '\0';
958  formatted_value.assign(formatted_value_);
959  }
960 
961  // Convert user_context to string as well.
962  std::string user_context;
963  if (user_context_null_ == MLM_FALSE) {
964  user_context_[user_context_length_] = '\0';
965  user_context.assign(user_context_);
966  }
967 
968  // Options are held in a binary or textual format in the database.
969  // This is similar to having an option specified in a server
970  // configuration file. Such option is converted to appropriate C++
971  // class, using option definition. Thus, we need to find the
972  // option definition for this option code and option space.
973 
974  // Check if this is a standard option.
975  OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
976 
977  // Otherwise, we may check if this an option encapsulated within the
978  // vendor space.
979  if (!def && (space != DHCP4_OPTION_SPACE) &&
980  (space != DHCP6_OPTION_SPACE)) {
981  uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
982  if (vendor_id > 0) {
983  def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
984  }
985  }
986 
987  // In all other cases, we use runtime option definitions, which
988  // should be also registered within the libdhcp++.
989  if (!def) {
990  def = LibDHCP::getRuntimeOptionDef(space, code_);
991  }
992 
993  OptionPtr option;
994 
995  if (!def) {
996  // If no definition found, we use generic option type.
997  OptionBuffer buf(value_, value_ + value_length_);
998  option.reset(new Option(universe_, code_, buf.begin(),
999  buf.end()));
1000  } else {
1001  // The option value may be specified in textual or binary format
1002  // in the database. If formatted_value is empty, the binary
1003  // format is used. Depending on the format we use a different
1004  // variant of the optionFactory function.
1005  if (formatted_value.empty()) {
1006  OptionBuffer buf(value_, value_ + value_length_);
1007  option = def->optionFactory(universe_, code_, buf.begin(),
1008  buf.end());
1009  } else {
1010  // Spit the value specified in comma separated values
1011  // format.
1012  std::vector<std::string> split_vec;
1013  boost::split(split_vec, formatted_value, boost::is_any_of(","));
1014  option = def->optionFactory(universe_, code_, split_vec);
1015  }
1016  }
1017 
1018  OptionDescriptor desc(option, persistent_, formatted_value);
1019 
1020  // Set the user context if there is one into the option descriptor.
1021  if (!user_context.empty()) {
1022  try {
1023  ConstElementPtr ctx = Element::fromJSON(user_context);
1024  if (!ctx || (ctx->getType() != Element::map)) {
1025  isc_throw(BadValue, "user context '" << user_context
1026  << "' is no a JSON map");
1027  }
1028  desc.setContext(ctx);
1029  } catch (const isc::data::JSONError& ex) {
1030  isc_throw(BadValue, "user context '" << user_context
1031  << "' is invalid JSON: " << ex.what());
1032  }
1033  }
1034 
1035  cfg->add(desc, space);
1036  }
1037 
1042  void setColumnNames(std::vector<std::string>& columns) {
1043  columns[option_id_index_] = "option_id";
1044  columns[code_index_] = "code";
1045  columns[value_index_] = "value";
1046  columns[formatted_value_index_] = "formatted_value";
1047  columns[space_index_] = "space";
1048  columns[persistent_index_] = "persistent";
1049  columns[user_context_index_] = "user_context";
1050  }
1051 
1057  void setBindFields(std::vector<MYSQL_BIND>& bind) {
1058  // This method is called just before making a new query, so we
1059  // reset the most_recent_option_id_ to start over with options
1060  // processing.
1061  most_recent_option_id_ = 0;
1062 
1063  // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
1064  bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
1065  bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
1066  bind[option_id_index_].is_unsigned = MLM_TRUE;
1067 
1068  // code : TINYINT OR SHORT UNSIGNED NOT NULL
1069  bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
1070  bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
1071  bind[code_index_].is_unsigned = MLM_TRUE;
1072  bind[code_index_].is_null = &code_null_;
1073 
1074  // value : BLOB NULL
1075  value_length_ = sizeof(value_);
1076  bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
1077  bind[value_index_].buffer = reinterpret_cast<char*>(value_);
1078  bind[value_index_].buffer_length = value_length_;
1079  bind[value_index_].length = &value_length_;
1080  bind[value_index_].is_null = &value_null_;
1081 
1082  // formatted_value : TEXT NULL
1083  formatted_value_length_ = sizeof(formatted_value_);
1084  bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
1085  bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
1086  bind[formatted_value_index_].buffer_length = formatted_value_length_;
1087  bind[formatted_value_index_].length = &formatted_value_length_;
1088  bind[formatted_value_index_].is_null = &formatted_value_null_;
1089 
1090  // space : VARCHAR(128) NULL
1091  space_length_ = sizeof(space_);
1092  bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
1093  bind[space_index_].buffer = reinterpret_cast<char*>(space_);
1094  bind[space_index_].buffer_length = space_length_;
1095  bind[space_index_].length = &space_length_;
1096  bind[space_index_].is_null = &space_null_;
1097 
1098  // persistent : TINYINT(1) NOT NULL DEFAULT 0
1099  bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
1100  bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
1101  bind[persistent_index_].is_unsigned = MLM_TRUE;
1102 
1103  // user_context : TEXT NULL
1104  user_context_length_ = sizeof(user_context_);
1105  bind[user_context_index_].buffer_type = MYSQL_TYPE_STRING;
1106  bind[user_context_index_].buffer = reinterpret_cast<char*>(user_context_);
1107  bind[user_context_index_].buffer_length = user_context_length_;
1108  bind[user_context_index_].length = &user_context_length_;
1109  bind[user_context_index_].is_null = &user_context_null_;
1110  }
1111 
1112  private:
1113 
1115  Option::Universe universe_;
1116 
1118  size_t start_column_;
1119 
1121  uint32_t option_id_;
1122 
1124  uint16_t code_;
1125 
1127  uint8_t value_[OPTION_VALUE_MAX_LEN];
1128 
1130  unsigned long value_length_;
1131 
1133  char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
1134 
1136  unsigned long formatted_value_length_;
1137 
1139  char space_[OPTION_SPACE_MAX_LEN];
1140 
1142  unsigned long space_length_;
1143 
1146  bool persistent_;
1147 
1149  char user_context_[USER_CONTEXT_MAX_LEN];
1150 
1152  unsigned long user_context_length_;
1153 
1156 
1157  my_bool option_id_null_;
1159 
1161  my_bool code_null_;
1162 
1164  my_bool value_null_;
1165 
1168  my_bool formatted_value_null_;
1169 
1171  my_bool space_null_;
1172 
1174  my_bool user_context_null_;
1176 
1178 
1179  size_t option_id_index_;
1181 
1183  size_t code_index_;
1184 
1186  size_t value_index_;
1187 
1189  size_t formatted_value_index_;
1190 
1192  size_t space_index_;
1193 
1195  size_t persistent_index_;
1197 
1199  size_t user_context_index_;
1200 
1202  uint32_t most_recent_option_id_;
1203  };
1204 
1206  typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
1207 
1208 public:
1209 
1216  enum FetchedOptions {
1217  DHCP4_ONLY,
1218  DHCP6_ONLY,
1219  DHCP4_AND_DHCP6
1220  };
1221 
1230  MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
1231  const size_t additional_columns_num = 0)
1232  : MySqlHostExchange(getRequiredColumnsNum(fetched_options)
1233  + additional_columns_num),
1234  opt_proc4_(), opt_proc6_() {
1235 
1236  // Create option processor for DHCPv4 options, if required.
1237  if ((fetched_options == DHCP4_ONLY) ||
1238  (fetched_options == DHCP4_AND_DHCP6)) {
1239  opt_proc4_.reset(new OptionProcessor(Option::V4,
1240  findAvailColumn()));
1241  opt_proc4_->setColumnNames(columns_);
1242  }
1243 
1244  // Create option processor for DHCPv6 options, if required.
1245  if ((fetched_options == DHCP6_ONLY) ||
1246  (fetched_options == DHCP4_AND_DHCP6)) {
1247  opt_proc6_.reset(new OptionProcessor(Option::V6,
1248  findAvailColumn()));
1249  opt_proc6_->setColumnNames(columns_);
1250  }
1251  }
1252 
1261  virtual void processFetchedData(ConstHostCollection& hosts) {
1262  // Holds pointer to the previously parsed host.
1263  HostPtr most_recent_host;
1264  if (!hosts.empty()) {
1265  // Const cast is not very elegant way to deal with it, but
1266  // there is a good reason to use it here. This method is called
1267  // to build a collection of const hosts to be returned to the
1268  // caller. If we wanted to use non-const collection we'd need
1269  // to copy the whole collection before returning it, which has
1270  // performance implications. Alternatively, we could store the
1271  // most recently added host in a class member but this would
1272  // make the code less readable.
1273  most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
1274  }
1275 
1276  // If no host has been parsed yet or we're at the row holding next
1277  // host, we create a new host object and put it at the end of the
1278  // list.
1279  if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
1280  HostPtr host = retrieveHost();
1281  hosts.push_back(host);
1282  most_recent_host = host;
1283  }
1284 
1285  // Parse DHCPv4 options if required to do so.
1286  if (opt_proc4_) {
1287  CfgOptionPtr cfg = most_recent_host->getCfgOption4();
1288  opt_proc4_->retrieveOption(cfg);
1289  }
1290 
1291  // Parse DHCPv6 options if required to do so.
1292  if (opt_proc6_) {
1293  CfgOptionPtr cfg = most_recent_host->getCfgOption6();
1294  opt_proc6_->retrieveOption(cfg);
1295  }
1296  }
1297 
1301  virtual std::vector<MYSQL_BIND> createBindForReceive() {
1302  // The following call sets bind_ values between 0 and 8.
1303  static_cast<void>(MySqlHostExchange::createBindForReceive());
1304 
1305  // Bind variables for DHCPv4 options.
1306  if (opt_proc4_) {
1307  opt_proc4_->setBindFields(bind_);
1308  }
1309 
1310  // Bind variables for DHCPv6 options.
1311  if (opt_proc6_) {
1312  opt_proc6_->setBindFields(bind_);
1313  }
1314 
1315  // Add the error flags
1316  setErrorIndicators(bind_, error_);
1317 
1318  return (bind_);
1319  };
1320 
1321 private:
1322 
1334  static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
1335  return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
1336  OPTION_COLUMNS);
1337  }
1338 
1342  OptionProcessorPtr opt_proc4_;
1343 
1347  OptionProcessorPtr opt_proc6_;
1348 };
1349 
1362 class MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
1363 private:
1364 
1366  static const size_t RESERVATION_COLUMNS = 5;
1367 
1368 public:
1369 
1374  MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
1375  : MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
1376  reservation_id_(0),
1377  reserv_type_(0), reserv_type_null_(MLM_FALSE),
1378  ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
1379  reservation_id_index_(findAvailColumn()),
1380  address_index_(reservation_id_index_ + 1),
1381  prefix_len_index_(reservation_id_index_ + 2),
1382  type_index_(reservation_id_index_ + 3),
1383  iaid_index_(reservation_id_index_ + 4),
1384  most_recent_reservation_id_(0) {
1385 
1386  memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
1387 
1388  // Provide names of additional columns returned by the queries.
1389  columns_[reservation_id_index_] = "reservation_id";
1390  columns_[address_index_] = "address";
1391  columns_[prefix_len_index_] = "prefix_len";
1392  columns_[type_index_] = "type";
1393  columns_[iaid_index_] = "dhcp6_iaid";
1394  }
1395 
1399  uint32_t getReservationId() const {
1400  if (reserv_type_null_ == MLM_FALSE) {
1401  return (reservation_id_);
1402  }
1403  return (0);
1404  };
1405 
1412  IPv6Resrv retrieveReservation() {
1413  // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
1414  IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
1415 
1416  switch (reserv_type_) {
1417  case 0:
1418  type = IPv6Resrv::TYPE_NA;
1419  break;
1420 
1421  case 2:
1422  type = IPv6Resrv::TYPE_PD;
1423  break;
1424 
1425  default:
1427  "invalid IPv6 reservation type returned: "
1428  << static_cast<int>(reserv_type_)
1429  << ". Only 0 or 2 are allowed.");
1430  }
1431 
1432  ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0';
1433  std::string address = ipv6_address_buffer_;
1434  IPv6Resrv r(type, IOAddress(address), prefix_len_);
1435  return (r);
1436  };
1437 
1457  virtual void processFetchedData(ConstHostCollection& hosts) {
1458 
1459  // Call parent class to fetch host information and options.
1460  MySqlHostWithOptionsExchange::processFetchedData(hosts);
1461 
1462  if (getReservationId() == 0) {
1463  return;
1464  }
1465 
1466  if (hosts.empty()) {
1467  isc_throw(Unexpected, "no host information while retrieving"
1468  " IPv6 reservation");
1469  }
1470  HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
1471 
1472  // If we're dealing with a new reservation, let's add it to the
1473  // host.
1474  if (getReservationId() > most_recent_reservation_id_) {
1475  most_recent_reservation_id_ = getReservationId();
1476 
1477  if (most_recent_reservation_id_ > 0) {
1478  host->addReservation(retrieveReservation());
1479  }
1480  }
1481  }
1482 
1491  virtual std::vector<MYSQL_BIND> createBindForReceive() {
1492  // Reset most recent reservation id value because we're now making
1493  // a new SELECT query.
1494  most_recent_reservation_id_ = 0;
1495 
1496  // Bind values supported by parent classes.
1497  static_cast<void>(MySqlHostWithOptionsExchange::createBindForReceive());
1498 
1499  // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
1500  bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
1501  bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
1502  bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
1503 
1504  // IPv6 address/prefix VARCHAR(39)
1505  ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
1506  bind_[address_index_].buffer_type = MYSQL_TYPE_STRING;
1507  bind_[address_index_].buffer = ipv6_address_buffer_;
1508  bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
1509  bind_[address_index_].length = &ipv6_address_buffer_len_;
1510 
1511  // prefix_len : TINYINT
1512  bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
1513  bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
1514  bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
1515 
1516  // (reservation) type : TINYINT
1517  reserv_type_null_ = MLM_FALSE;
1518  bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
1519  bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
1520  bind_[type_index_].is_unsigned = MLM_TRUE;
1521  bind_[type_index_].is_null = &reserv_type_null_;
1522 
1523  // dhcp6_iaid INT UNSIGNED
1524  bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
1525  bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
1526  bind_[iaid_index_].is_unsigned = MLM_TRUE;
1527 
1528  // Add the error flags
1529  setErrorIndicators(bind_, error_);
1530 
1531  return (bind_);
1532  };
1533 
1534 private:
1535 
1537  uint32_t reservation_id_;
1538 
1540  uint8_t reserv_type_;
1541 
1546  my_bool reserv_type_null_;
1547 
1549  char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];
1550 
1552  unsigned long ipv6_address_buffer_len_;
1553 
1555  uint8_t prefix_len_;
1556 
1558  uint32_t iaid_;
1559 
1561 
1562  size_t reservation_id_index_;
1564 
1566  size_t address_index_;
1567 
1569  size_t prefix_len_index_;
1570 
1572  size_t type_index_;
1573 
1575  size_t iaid_index_;
1576 
1578 
1580  uint32_t most_recent_reservation_id_;
1581 };
1582 
1593 class MySqlIPv6ReservationExchange {
1594 private:
1595 
1597  static const size_t RESRV_COLUMNS = 6;
1598 
1599 public:
1600 
1604  MySqlIPv6ReservationExchange()
1605  : host_id_(0), address_("::"), address_len_(0), prefix_len_(0), type_(0),
1606  iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
1607 
1608  // Reset error table.
1609  std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);
1610 
1611  // Set the column names (for error messages)
1612  columns_[0] = "host_id";
1613  columns_[1] = "address";
1614  columns_[2] = "prefix_len";
1615  columns_[3] = "type";
1616  columns_[4] = "dhcp6_iaid";
1617  BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
1618  }
1619 
1632  std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
1633  const HostID& id) {
1634 
1635  // Store the values to ensure they remain valid.
1636  resv_ = resv;
1637  host_id_ = id;
1638 
1639  // Initialize prior to constructing the array of MYSQL_BIND structures.
1640  // It sets all fields, including is_null, to zero, so we need to set
1641  // is_null only if it should be true. This gives up minor performance
1642  // benefit while being safe approach. For improved readability, the
1643  // code that explicitly sets is_null is there, but is commented out.
1644  memset(bind_, 0, sizeof(bind_));
1645 
1646  // Set up the structures for the various components of the host structure.
1647 
1648  try {
1649  // address VARCHAR(39)
1650  address_ = resv.getPrefix().toText();
1651  address_len_ = address_.length();
1652  bind_[0].buffer_type = MYSQL_TYPE_BLOB;
1653  bind_[0].buffer = reinterpret_cast<char*>
1654  (const_cast<char*>(address_.c_str()));
1655  bind_[0].buffer_length = address_len_;
1656  bind_[0].length = &address_len_;
1657 
1658  // prefix_len tinyint
1659  prefix_len_ = resv.getPrefixLen();
1660  bind_[1].buffer_type = MYSQL_TYPE_TINY;
1661  bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
1662  bind_[1].is_unsigned = MLM_TRUE;
1663 
1664  // type tinyint
1665  // See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
1666  type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
1667  bind_[2].buffer_type = MYSQL_TYPE_TINY;
1668  bind_[2].buffer = reinterpret_cast<char*>(&type_);
1669  bind_[2].is_unsigned = MLM_TRUE;
1670 
1671  // dhcp6_iaid INT UNSIGNED
1673  iaid_ = 0;
1674  bind_[3].buffer_type = MYSQL_TYPE_LONG;
1675  bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
1676  bind_[3].is_unsigned = MLM_TRUE;
1677 
1678  // host_id INT UNSIGNED NOT NULL
1679  bind_[4].buffer_type = MYSQL_TYPE_LONG;
1680  bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
1681  bind_[4].is_unsigned = MLM_TRUE;
1682 
1683  } catch (const std::exception& ex) {
1685  "Could not create bind array from IPv6 Reservation: "
1686  << resv_.toText() << ", reason: " << ex.what());
1687  }
1688 
1689  // Add the data to the vector. Note the end element is one after the
1690  // end of the array.
1691  // RESRV_COLUMNS -1 as we do not set reservation_id.
1692  return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[RESRV_COLUMNS-1]));
1693  }
1694 
1695 private:
1696 
1698  uint64_t host_id_;
1699 
1701  std::string address_;
1702 
1704  unsigned long address_len_;
1705 
1707  uint8_t prefix_len_;
1708 
1710  uint8_t type_;
1711 
1713  uint8_t iaid_;
1714 
1716  IPv6Resrv resv_;
1717 
1719  MYSQL_BIND bind_[RESRV_COLUMNS];
1720 
1722  std::string columns_[RESRV_COLUMNS];
1723 
1726  my_bool error_[RESRV_COLUMNS];
1727 };
1728 
1732 class MySqlOptionExchange {
1733 private:
1734 
1736  static const size_t OPTION_COLUMNS = 10;
1737 
1738 public:
1739 
1741  MySqlOptionExchange()
1742 
1743  : type_(0), value_len_(0), formatted_value_len_(0), space_(),
1744  space_len_(0), persistent_(false), user_context_(),
1745  user_context_len_(0), client_class_(), client_class_len_(0),
1746  subnet_id_(SUBNET_ID_UNUSED), host_id_(0), option_() {
1747 
1748  BOOST_STATIC_ASSERT(9 < OPTION_COLUMNS);
1749  }
1750 
1754  std::vector<MYSQL_BIND>
1755  createBindForSend(const OptionDescriptor& opt_desc,
1756  const std::string& opt_space,
1757  const OptionalValue<SubnetID>& subnet_id,
1758  const HostID& host_id) {
1759 
1760  // Hold pointer to the option to make sure it remains valid until
1761  // we complete a query.
1762  option_ = opt_desc.option_;
1763 
1764  memset(bind_, 0, sizeof(bind_));
1765 
1766  try {
1767  // option_id: INT UNSIGNED NOT NULL
1768  // The option_id is auto_incremented, so we need to pass the NULL
1769  // value.
1770  bind_[0].buffer_type = MYSQL_TYPE_NULL;
1771 
1772  // code: SMALLINT UNSIGNED NOT NULL
1773  type_ = option_->getType();
1774  bind_[1].buffer_type = MYSQL_TYPE_SHORT;
1775  bind_[1].buffer = reinterpret_cast<char*>(&type_);
1776  bind_[1].is_unsigned = MLM_TRUE;
1777 
1778  // value: BLOB NULL
1779  if (opt_desc.formatted_value_.empty() &&
1780  (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
1781  // The formatted_value is empty and the option value is
1782  // non-empty so we need to prepare on-wire format for the
1783  // option and store it in the database as a blob.
1784  OutputBuffer buf(opt_desc.option_->len());
1785  opt_desc.option_->pack(buf);
1786  const char* buf_ptr = static_cast<const char*>(buf.getData());
1787  value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
1788  buf_ptr + buf.getLength());
1789  value_len_ = value_.size();
1790  bind_[2].buffer_type = MYSQL_TYPE_BLOB;
1791  bind_[2].buffer = &value_[0];
1792  bind_[2].buffer_length = value_len_;
1793  bind_[2].length = &value_len_;
1794 
1795  } else {
1796  // No value or formatted_value specified. In this case, the
1797  // value blob is NULL.
1798  value_.clear();
1799  bind_[2].buffer_type = MYSQL_TYPE_NULL;
1800  }
1801 
1802  // formatted_value: TEXT NULL,
1803  if (!opt_desc.formatted_value_.empty()) {
1804  formatted_value_len_ = opt_desc.formatted_value_.size();
1805  bind_[3].buffer_type = MYSQL_TYPE_STRING;
1806  bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
1807  bind_[3].buffer_length = formatted_value_len_;
1808  bind_[3].length = &formatted_value_len_;
1809 
1810  } else {
1811  bind_[3].buffer_type = MYSQL_TYPE_NULL;
1812  }
1813 
1814  // space: VARCHAR(128) NULL
1815  space_ = opt_space;
1816  space_len_ = space_.size();
1817  bind_[4].buffer_type = MYSQL_TYPE_STRING;
1818  bind_[4].buffer = const_cast<char*>(space_.c_str());
1819  bind_[4].buffer_length = space_len_;
1820  bind_[4].length = &space_len_;
1821 
1822  // persistent: TINYINT(1) NOT NULL DEFAULT 0
1823  persistent_ = opt_desc.persistent_;
1824  bind_[5].buffer_type = MYSQL_TYPE_TINY;
1825  bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
1826  bind_[5].is_unsigned = MLM_TRUE;
1827 
1828  // user_context: TEST NULL,
1829  ConstElementPtr ctx = opt_desc.getContext();
1830  if (ctx) {
1831  user_context_ = ctx->str();
1832  user_context_len_ = user_context_.size();
1833  bind_[6].buffer_type = MYSQL_TYPE_STRING;
1834  bind_[6].buffer = const_cast<char*>(user_context_.c_str());
1835  bind_[6].buffer_length = user_context_len_;
1836  bind_[6].length = &user_context_len_;
1837  } else {
1838  bind_[6].buffer_type = MYSQL_TYPE_NULL;
1839  }
1840 
1841  // dhcp_client_class: VARCHAR(128) NULL
1842  client_class_len_ = client_class_.size();
1843  bind_[7].buffer_type = MYSQL_TYPE_STRING;
1844  bind_[7].buffer = const_cast<char*>(client_class_.c_str());
1845  bind_[7].buffer_length = client_class_len_;
1846  bind_[7].length = &client_class_len_;
1847 
1848  // dhcp4_subnet_id: INT UNSIGNED NULL
1849  if (subnet_id.isSpecified()) {
1850  subnet_id_ = subnet_id;
1851  bind_[8].buffer_type = MYSQL_TYPE_LONG;
1852  bind_[8].buffer = reinterpret_cast<char*>(subnet_id_);
1853  bind_[8].is_unsigned = MLM_TRUE;
1854 
1855  } else {
1856  bind_[8].buffer_type = MYSQL_TYPE_NULL;
1857  }
1858 
1859  // host_id: INT UNSIGNED NOT NULL
1860  host_id_ = host_id;
1861  bind_[9].buffer_type = MYSQL_TYPE_LONG;
1862  bind_[9].buffer = reinterpret_cast<char*>(&host_id_);
1863  bind_[9].is_unsigned = MLM_TRUE;
1864 
1865  } catch (const std::exception& ex) {
1867  "Could not create bind array for inserting DHCP "
1868  "option: " << option_->toText() << ", reason: "
1869  << ex.what());
1870  }
1871 
1872  return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
1873  }
1874 
1875 private:
1876 
1878  uint16_t type_;
1879 
1881  std::vector<uint8_t> value_;
1882 
1884  unsigned long value_len_;
1885 
1887  unsigned long formatted_value_len_;
1888 
1890  std::string space_;
1891 
1893  unsigned long space_len_;
1894 
1897  bool persistent_;
1898 
1900  std::string user_context_;
1901 
1903  unsigned long user_context_len_;
1904 
1906  std::string client_class_;
1907 
1909  unsigned long client_class_len_;
1910 
1912  uint32_t subnet_id_;
1913 
1915  uint32_t host_id_;
1916 
1918  OptionPtr option_;
1919 
1921  MYSQL_BIND bind_[OPTION_COLUMNS];
1922 };
1923 
1924 } // end of anonymous namespace
1925 
1926 namespace isc {
1927 namespace dhcp {
1928 
1931 public:
1932 
1940  GET_HOST_DHCPID, // Gets hosts by host identifier
1941  GET_HOST_ADDR, // Gets hosts by IPv4 address
1942  GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
1943  GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
1944  GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
1945  GET_HOST_PREFIX, // Gets host by IPv6 prefix
1946  GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
1947  INSERT_HOST, // Insert new host to collection
1948  INSERT_V6_RESRV, // Insert v6 reservation
1949  INSERT_V4_OPTION, // Insert DHCPv4 option
1950  INSERT_V6_OPTION, // Insert DHCPv6 option
1951  DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
1952  DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
1953  DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
1954  NUM_STATEMENTS // Number of statements
1955  };
1956 
1962  static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
1963 
1968  MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters);
1969 
1972 
1979  //
1986  std::pair<uint32_t, uint32_t> getVersion() const;
1987 
1995  void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
1996  std::vector<MYSQL_BIND>& bind);
1997 
2004  bool
2005  delStatement(StatementIndex stindex, MYSQL_BIND* bind);
2006 
2011  void addResv(const IPv6Resrv& resv, const HostID& id);
2012 
2021  void addOption(const MySqlHostDataSourceImpl::StatementIndex& stindex,
2022  const OptionDescriptor& opt_desc,
2023  const std::string& opt_space,
2024  const OptionalValue<SubnetID>& subnet_id,
2025  const HostID& host_id);
2026 
2033  void addOptions(const StatementIndex& stindex, const ConstCfgOptionPtr& options_cfg,
2034  const uint64_t host_id);
2035 
2046  void checkError(const int status, const StatementIndex index,
2047  const char* what) const;
2048 
2066  void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
2067  boost::shared_ptr<MySqlHostExchange> exchange,
2068  ConstHostCollection& result, bool single) const;
2069 
2086  ConstHostPtr getHost(const SubnetID& subnet_id,
2087  const Host::IdentifierType& identifier_type,
2088  const uint8_t* identifier_begin,
2089  const size_t identifier_len,
2090  StatementIndex stindex,
2091  boost::shared_ptr<MySqlHostExchange> exchange) const;
2092 
2100  void checkReadOnly() const;
2101 
2104  boost::shared_ptr<MySqlHostWithOptionsExchange> host_exchange_;
2105 
2108  boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_exchange_;
2109 
2113  boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv46_exchange_;
2114 
2117  boost::shared_ptr<MySqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
2118 
2122  boost::shared_ptr<MySqlOptionExchange> host_option_exchange_;
2123 
2126 
2129 };
2130 
2132 typedef boost::array<TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS>
2134 
2138  // Retrieves host information, IPv6 reservations and both DHCPv4 and
2139  // DHCPv6 options associated with the host. The LEFT JOIN clause is used
2140  // to retrieve information from 4 different tables using a single query.
2141  // Hence, this query returns multiple rows for a single host.
2142  {MySqlHostDataSourceImpl::GET_HOST_DHCPID,
2143  "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
2144  "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
2145  "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
2146  "h.user_context, "
2147  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2148  "h.dhcp4_boot_file_name, h.auth_key, "
2149  "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
2150  "o4.persistent, o4.user_context, "
2151  "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
2152  "o6.persistent, o6.user_context, "
2153  "r.reservation_id, r.address, r.prefix_len, r.type, "
2154  "r.dhcp6_iaid "
2155  "FROM hosts AS h "
2156  "LEFT JOIN dhcp4_options AS o4 "
2157  "ON h.host_id = o4.host_id "
2158  "LEFT JOIN dhcp6_options AS o6 "
2159  "ON h.host_id = o6.host_id "
2160  "LEFT JOIN ipv6_reservations AS r "
2161  "ON h.host_id = r.host_id "
2162  "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
2163  "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
2164 
2165  // Retrieves host information along with the DHCPv4 options associated with
2166  // it. Left joining the dhcp4_options table results in multiple rows being
2167  // returned for the same host. The host is retrieved by IPv4 address.
2168  {MySqlHostDataSourceImpl::GET_HOST_ADDR,
2169  "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
2170  "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2171  "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2172  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2173  "h.dhcp4_boot_file_name, h.auth_key, "
2174  "o.option_id, o.code, o.value, o.formatted_value, o.space, "
2175  "o.persistent, o.user_context "
2176  "FROM hosts AS h "
2177  "LEFT JOIN dhcp4_options AS o "
2178  "ON h.host_id = o.host_id "
2179  "WHERE ipv4_address = ? "
2180  "ORDER BY h.host_id, o.option_id"},
2181 
2182  // Retrieves host information and DHCPv4 options using subnet identifier
2183  // and client's identifier. Left joining the dhcp4_options table results in
2184  // multiple rows being returned for the same host.
2185  {MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
2186  "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
2187  "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2188  "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2189  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2190  "h.dhcp4_boot_file_name, h.auth_key, "
2191  ""
2192  "o.option_id, o.code, o.value, o.formatted_value, o.space, "
2193  "o.persistent, o.user_context "
2194  "FROM hosts AS h "
2195  "LEFT JOIN dhcp4_options AS o "
2196  "ON h.host_id = o.host_id "
2197  "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
2198  " AND h.dhcp_identifier = ? "
2199  "ORDER BY h.host_id, o.option_id"},
2200 
2201  // Retrieves host information, IPv6 reservations and DHCPv6 options
2202  // associated with a host. The number of rows returned is a multiplication
2203  // of number of IPv6 reservations and DHCPv6 options.
2204  {MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
2205  "SELECT h.host_id, h.dhcp_identifier, "
2206  "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2207  "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2208  "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2209  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2210  "h.dhcp4_boot_file_name, h.auth_key, "
2211  "o.option_id, o.code, o.value, o.formatted_value, o.space, "
2212  "o.persistent, o.user_context, "
2213  "r.reservation_id, r.address, r.prefix_len, r.type, "
2214  "r.dhcp6_iaid "
2215  "FROM hosts AS h "
2216  "LEFT JOIN dhcp6_options AS o "
2217  "ON h.host_id = o.host_id "
2218  "LEFT JOIN ipv6_reservations AS r "
2219  "ON h.host_id = r.host_id "
2220  "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
2221  "AND h.dhcp_identifier = ? "
2222  "ORDER BY h.host_id, o.option_id, r.reservation_id"},
2223 
2224  // Retrieves host information and DHCPv4 options for the host using subnet
2225  // identifier and IPv4 reservation. Left joining the dhcp4_options table
2226  // results in multiple rows being returned for the host. The number of
2227  // rows depends on the number of options defined for the host.
2228  {MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
2229  "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
2230  "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2231  "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2232  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2233  "h.dhcp4_boot_file_name, h.auth_key, "
2234  "o.option_id, o.code, o.value, o.formatted_value, o.space, "
2235  "o.persistent, o.user_context "
2236  "FROM hosts AS h "
2237  "LEFT JOIN dhcp4_options AS o "
2238  "ON h.host_id = o.host_id "
2239  "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
2240  "ORDER BY h.host_id, o.option_id"},
2241 
2242  // Retrieves host information, IPv6 reservations and DHCPv6 options
2243  // associated with a host using prefix and prefix length. This query
2244  // returns host information for a single host. However, multiple rows
2245  // are returned due to left joining IPv6 reservations and DHCPv6 options.
2246  // The number of rows returned is multiplication of number of existing
2247  // IPv6 reservations and DHCPv6 options.
2248  {MySqlHostDataSourceImpl::GET_HOST_PREFIX,
2249  "SELECT h.host_id, h.dhcp_identifier, "
2250  "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2251  "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2252  "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2253  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2254  "h.dhcp4_boot_file_name, h.auth_key, "
2255  "o.option_id, o.code, o.value, o.formatted_value, o.space, "
2256  "o.persistent, o.user_context,"
2257  "r.reservation_id, r.address, r.prefix_len, r.type, "
2258  "r.dhcp6_iaid "
2259  "FROM hosts AS h "
2260  "LEFT JOIN dhcp6_options AS o "
2261  "ON h.host_id = o.host_id "
2262  "LEFT JOIN ipv6_reservations AS r "
2263  "ON h.host_id = r.host_id "
2264  "WHERE h.host_id = "
2265  "(SELECT host_id FROM ipv6_reservations "
2266  "WHERE address = ? AND prefix_len = ?) "
2267  "ORDER BY h.host_id, o.option_id, r.reservation_id"},
2268 
2269  // Retrieves host information, IPv6 reservations and DHCPv6 options
2270  // associated with a host using subnet id and prefix. This query
2271  // returns host information for a single host. However, multiple rows
2272  // are returned due to left joining IPv6 reservations and DHCPv6 options.
2273  // The number of rows returned is multiplication of number of existing
2274  // IPv6 reservations and DHCPv6 options.
2275  {MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
2276  "SELECT h.host_id, h.dhcp_identifier, "
2277  "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2278  "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2279  "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2280 
2281  "h.dhcp4_next_server, h.dhcp4_server_hostname, "
2282  "h.dhcp4_boot_file_name, h.auth_key, "
2283  "o.option_id, o.code, o.value, o.formatted_value, o.space, "
2284  "o.persistent, o.user_context, "
2285  "r.reservation_id, r.address, r.prefix_len, r.type, "
2286  "r.dhcp6_iaid "
2287  "FROM hosts AS h "
2288  "LEFT JOIN dhcp6_options AS o "
2289  "ON h.host_id = o.host_id "
2290  "LEFT JOIN ipv6_reservations AS r "
2291  "ON h.host_id = r.host_id "
2292  "WHERE h.dhcp6_subnet_id = ? AND r.address = ? "
2293  "ORDER BY h.host_id, o.option_id, r.reservation_id"},
2294 
2295  // Inserts a host into the 'hosts' table.
2296  {MySqlHostDataSourceImpl::INSERT_HOST,
2297  "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
2298  "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2299  "dhcp4_client_classes, dhcp6_client_classes, "
2300  "user_context, dhcp4_next_server, "
2301  "dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
2302  "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
2303 
2304  // Inserts a single IPv6 reservation into 'reservations' table.
2305  {MySqlHostDataSourceImpl::INSERT_V6_RESRV,
2306  "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2307  "dhcp6_iaid, host_id) "
2308  "VALUES (?,?,?,?,?)"},
2309 
2310  // Inserts a single DHCPv4 option into 'dhcp4_options' table.
2311  // Using fixed scope_id = 3, which associates an option with host.
2312  {MySqlHostDataSourceImpl::INSERT_V4_OPTION,
2313  "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
2314  "persistent, user_context, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) "
2315  " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
2316 
2317  // Inserts a single DHCPv6 option into 'dhcp6_options' table.
2318  // Using fixed scope_id = 3, which associates an option with host.
2319  {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
2320  "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
2321  "persistent, user_context, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
2322  " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
2323 
2324  {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
2325  "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
2326 
2327  {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
2328  "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
2329  "AND dhcp_identifier = ?"},
2330 
2331  {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
2332  "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
2333  "AND dhcp_identifier = ?"}
2334 
2335  }
2336 };
2337 
2338 MySqlHostDataSourceImpl::
2339 MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
2340  : host_exchange_(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY)),
2341  host_ipv6_exchange_(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP6_ONLY)),
2342  host_ipv46_exchange_(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::
2343  DHCP4_AND_DHCP6)),
2344  host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()),
2345  host_option_exchange_(new MySqlOptionExchange()),
2346  conn_(parameters),
2347  is_readonly_(false) {
2348 
2349  // Open the database.
2350  conn_.openDatabase();
2351 
2352  // Test schema version before we try to prepare statements.
2353  std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
2355  std::pair<uint32_t, uint32_t> db_version = getVersion();
2356  if (code_version != db_version) {
2357  isc_throw(DbOpenError, "MySQL schema version mismatch: need version: "
2358  << code_version.first << "." << code_version.second
2359  << " found version: " << db_version.first << "."
2360  << db_version.second);
2361  }
2362 
2363  // Enable autocommit. In case transaction is explicitly used, this
2364  // setting will be overwritten for the transaction. However, there are
2365  // cases when lack of autocommit could cause transactions to hang
2366  // until commit or rollback is explicitly called. This already
2367  // caused issues for some unit tests which were unable to cleanup
2368  // the database after the test because of pending transactions.
2369  // Use of autocommit will eliminate this problem.
2370  my_bool result = mysql_autocommit(conn_.mysql_, 1);
2371  if (result != 0) {
2372  isc_throw(DbOperationError, mysql_error(conn_.mysql_));
2373  }
2374 
2375  // Prepare query statements. Those are will be only used to retrieve
2376  // information from the database, so they can be used even if the
2377  // database is read only for the current user.
2380 
2381  // Check if the backend is explicitly configured to operate with
2382  // read only access to the database.
2384 
2385  // If we are using read-write mode for the database we also prepare
2386  // statements for INSERTS etc.
2387  if (!is_readonly_) {
2388  // Prepare statements for writing to the database, e.g. INSERT.
2390  tagged_statements.end());
2391  } else {
2392  LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_READONLY);
2393  }
2394 }
2395 
2397  // Free up the prepared statements, ignoring errors. (What would we do
2398  // about them? We're destroying this object and are not really concerned
2399  // with errors on a database connection that is about to go away.)
2400  for (int i = 0; i < conn_.statements_.size(); ++i) {
2401  if (conn_.statements_[i] != NULL) {
2402  (void) mysql_stmt_close(conn_.statements_[i]);
2403  conn_.statements_[i] = NULL;
2404  }
2405  }
2406 
2407  // There is no need to close the database in this destructor: it is
2408  // closed in the destructor of the mysql_ member variable.
2409 }
2410 
2411 std::pair<uint32_t, uint32_t>
2414  DHCPSRV_MYSQL_HOST_DB_GET_VERSION);
2415 
2416  // Allocate a new statement.
2417  MYSQL_STMT *stmt = mysql_stmt_init(conn_.mysql_);
2418  if (stmt == NULL) {
2419  isc_throw(DbOperationError, "unable to allocate MySQL prepared "
2420  "statement structure, reason: " << mysql_error(conn_.mysql_));
2421  }
2422 
2423  // Prepare the statement from SQL text.
2424  const char* version_sql = "SELECT version, minor FROM schema_version";
2425  int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql));
2426  if (status != 0) {
2427  isc_throw(DbOperationError, "unable to prepare MySQL statement <"
2428  << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
2429  }
2430 
2431  // Execute the prepared statement.
2432  if (mysql_stmt_execute(stmt) != 0) {
2433  isc_throw(DbOperationError, "cannot execute schema version query <"
2434  << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
2435  }
2436 
2437  // Bind the output of the statement to the appropriate variables.
2438  MYSQL_BIND bind[2];
2439  memset(bind, 0, sizeof(bind));
2440 
2441  uint32_t major;
2442  bind[0].buffer_type = MYSQL_TYPE_LONG;
2443  bind[0].is_unsigned = 1;
2444  bind[0].buffer = &major;
2445  bind[0].buffer_length = sizeof(major);
2446 
2447  uint32_t minor;
2448  bind[1].buffer_type = MYSQL_TYPE_LONG;
2449  bind[1].is_unsigned = 1;
2450  bind[1].buffer = &minor;
2451  bind[1].buffer_length = sizeof(minor);
2452 
2453  if (mysql_stmt_bind_result(stmt, bind)) {
2454  isc_throw(DbOperationError, "unable to bind result set for <"
2455  << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
2456  }
2457 
2458  // Fetch the data.
2459  if (mysql_stmt_fetch(stmt)) {
2460  mysql_stmt_close(stmt);
2461  isc_throw(DbOperationError, "unable to bind result set for <"
2462  << version_sql << ">, reason: " << mysql_errno(conn_.mysql_));
2463  }
2464 
2465  // Discard the statement and its resources
2466  mysql_stmt_close(stmt);
2467 
2468  return (std::make_pair(major, minor));
2469 }
2470 
2471 
2472 void
2474  std::vector<MYSQL_BIND>& bind) {
2475 
2476  // Bind the parameters to the statement
2477  int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
2478  checkError(status, stindex, "unable to bind parameters");
2479 
2480  // Execute the statement
2481  status = mysql_stmt_execute(conn_.statements_[stindex]);
2482 
2483  if (status != 0) {
2484  // Failure: check for the special case of duplicate entry.
2485  if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
2486  isc_throw(DuplicateEntry, "Database duplicate entry error");
2487  }
2488  checkError(status, stindex, "unable to execute");
2489  }
2490 }
2491 
2492 bool
2494  MYSQL_BIND* bind) {
2495  // Bind the parameters to the statement
2496  int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
2497  checkError(status, stindex, "unable to bind parameters");
2498 
2499  // Execute the statement
2500  status = mysql_stmt_execute(conn_.statements_[stindex]);
2501 
2502  if (status != 0) {
2503  checkError(status, stindex, "unable to execute");
2504  }
2505 
2506  // Let's check how many hosts were deleted.
2507  my_ulonglong numrows = mysql_stmt_affected_rows(conn_.statements_[stindex]);
2508  return (numrows != 0);
2509 }
2510 
2511 void
2513  const HostID& id) {
2514  std::vector<MYSQL_BIND> bind =
2515  host_ipv6_reservation_exchange_->createBindForSend(resv, id);
2516 
2518 }
2519 
2520 void
2522  const OptionDescriptor& opt_desc,
2523  const std::string& opt_space,
2524  const OptionalValue<SubnetID>& subnet_id,
2525  const HostID& id) {
2526  std::vector<MYSQL_BIND> bind =
2527  host_option_exchange_->createBindForSend(opt_desc, opt_space,
2528  subnet_id, id);
2529 
2530  addStatement(stindex, bind);
2531 }
2532 
2533 void
2535  const ConstCfgOptionPtr& options_cfg,
2536  const uint64_t host_id) {
2537  // Get option space names and vendor space names and combine them within a
2538  // single list.
2539  std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
2540  std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
2541  option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
2542  vendor_spaces.end());
2543 
2544  // For each option space retrieve all options and insert them into the
2545  // database.
2546  for (std::list<std::string>::const_iterator space = option_spaces.begin();
2547  space != option_spaces.end(); ++space) {
2548  OptionContainerPtr options = options_cfg->getAll(*space);
2549  if (options && !options->empty()) {
2550  for (OptionContainer::const_iterator opt = options->begin();
2551  opt != options->end(); ++opt) {
2552  addOption(stindex, *opt, *space, OptionalValue<SubnetID>(),
2553  host_id);
2554  }
2555  }
2556  }
2557 }
2558 
2559 void
2561 checkError(const int status, const StatementIndex index,
2562  const char* what) const {
2563  conn_.checkError(status, index, what);
2564 }
2565 
2566 void
2568 getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
2569  boost::shared_ptr<MySqlHostExchange> exchange,
2570  ConstHostCollection& result, bool single) const {
2571 
2572  // Bind the selection parameters to the statement
2573  int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
2574  checkError(status, stindex, "unable to bind WHERE clause parameter");
2575 
2576  // Set up the MYSQL_BIND array for the data being returned and bind it to
2577  // the statement.
2578  std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
2579  status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
2580  checkError(status, stindex, "unable to bind SELECT clause parameters");
2581 
2582  // Execute the statement
2583  status = mysql_stmt_execute(conn_.statements_[stindex]);
2584  checkError(status, stindex, "unable to execute");
2585 
2586  // Ensure that all the lease information is retrieved in one go to avoid
2587  // overhead of going back and forth between client and server.
2588  status = mysql_stmt_store_result(conn_.statements_[stindex]);
2589  checkError(status, stindex, "unable to set up for storing all results");
2590 
2591  // Set up the fetch "release" object to release resources associated
2592  // with the call to mysql_stmt_fetch when this method exits, then
2593  // retrieve the data. mysql_stmt_fetch return value equal to 0 represents
2594  // successful data fetch.
2595  MySqlFreeResult fetch_release(conn_.statements_[stindex]);
2596  while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) ==
2598  try {
2599  exchange->processFetchedData(result);
2600 
2601  } catch (const isc::BadValue& ex) {
2602  // Rethrow the exception with a bit more data.
2603  isc_throw(BadValue, ex.what() << ". Statement is <" <<
2604  conn_.text_statements_[stindex] << ">");
2605  }
2606 
2607  if (single && (result.size() > 1)) {
2608  isc_throw(MultipleRecords, "multiple records were found in the "
2609  "database where only one was expected for query "
2610  << conn_.text_statements_[stindex]);
2611  }
2612  }
2613 
2614  // How did the fetch end?
2615  // If mysql_stmt_fetch return value is equal to 1 an error occurred.
2616  if (status == MLM_MYSQL_FETCH_FAILURE) {
2617  // Error - unable to fetch results
2618  checkError(status, stindex, "unable to fetch results");
2619 
2620  } else if (status == MYSQL_DATA_TRUNCATED) {
2621  // Data truncated - throw an exception indicating what was at fault
2623  << " returned truncated data: columns affected are "
2624  << exchange->getErrorColumns());
2625  }
2626 }
2627 
2630 getHost(const SubnetID& subnet_id,
2631  const Host::IdentifierType& identifier_type,
2632  const uint8_t* identifier_begin,
2633  const size_t identifier_len,
2634  StatementIndex stindex,
2635  boost::shared_ptr<MySqlHostExchange> exchange) const {
2636 
2637  // Set up the WHERE clause value
2638  MYSQL_BIND inbind[3];
2639  memset(inbind, 0, sizeof(inbind));
2640 
2641  uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
2642  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2643  inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
2644  inbind[0].is_unsigned = MLM_TRUE;
2645 
2646  // Identifier value.
2647  std::vector<char> identifier_vec(identifier_begin,
2648  identifier_begin + identifier_len);
2649  unsigned long length = identifier_vec.size();
2650  inbind[2].buffer_type = MYSQL_TYPE_BLOB;
2651  inbind[2].buffer = &identifier_vec[0];
2652  inbind[2].buffer_length = length;
2653  inbind[2].length = &length;
2654 
2655  // Identifier type.
2656  char identifier_type_copy = static_cast<char>(identifier_type);
2657  inbind[1].buffer_type = MYSQL_TYPE_TINY;
2658  inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
2659  inbind[1].is_unsigned = MLM_TRUE;
2660 
2661  ConstHostCollection collection;
2662  getHostCollection(stindex, inbind, exchange, collection, true);
2663 
2664  // Return single record if present, else clear the host.
2665  ConstHostPtr result;
2666  if (!collection.empty())
2667  result = *collection.begin();
2668 
2669  return (result);
2670 }
2671 
2672 void
2674  if (is_readonly_) {
2675  isc_throw(ReadOnlyDb, "MySQL host database backend is configured to"
2676  " operate in read only mode");
2677  }
2678 }
2679 
2681 MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters)
2682  : impl_(new MySqlHostDataSourceImpl(parameters)) {
2683 }
2684 
2686  delete impl_;
2687 }
2688 
2689 void
2691  // If operating in read-only mode, throw exception.
2692  impl_->checkReadOnly();
2693 
2694  // Initiate MySQL transaction as we will have to make multiple queries
2695  // to insert host information into multiple tables. If that fails on
2696  // any stage, the transaction will be rolled back by the destructor of
2697  // the MySqlTransaction class.
2698  MySqlTransaction transaction(impl_->conn_);
2699 
2700  // Create the MYSQL_BIND array for the host
2701  std::vector<MYSQL_BIND> bind = impl_->host_exchange_->createBindForSend(host);
2702 
2703  // ... and insert the host.
2705 
2706  // Gets the last inserted hosts id
2707  uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
2708 
2709  // Insert DHCPv4 options.
2710  ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
2711  if (cfg_option4) {
2713  cfg_option4, host_id);
2714  }
2715 
2716  // Insert DHCPv6 options.
2717  ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
2718  if (cfg_option6) {
2720  cfg_option6, host_id);
2721  }
2722 
2723  // Insert IPv6 reservations.
2724  IPv6ResrvRange v6resv = host->getIPv6Reservations();
2725  if (std::distance(v6resv.first, v6resv.second) > 0) {
2726  for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
2727  ++resv) {
2728  impl_->addResv(resv->second, host_id);
2729  }
2730  }
2731 
2732  // Everything went fine, so explicitly commit the transaction.
2733  transaction.commit();
2734 }
2735 
2736 bool
2738  // If operating in read-only mode, throw exception.
2739  impl_->checkReadOnly();
2740 
2741  if (addr.isV4()) {
2742  // Set up the WHERE clause value
2743  MYSQL_BIND inbind[2];
2744 
2745  uint32_t subnet = subnet_id;
2746  memset(inbind, 0, sizeof(inbind));
2747  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2748  inbind[0].buffer = reinterpret_cast<char*>(&subnet);
2749  inbind[0].is_unsigned = MLM_TRUE;
2750 
2751  uint32_t addr4 = addr.toUint32();
2752  inbind[1].buffer_type = MYSQL_TYPE_LONG;
2753  inbind[1].buffer = reinterpret_cast<char*>(&addr4);
2754  inbind[1].is_unsigned = MLM_TRUE;
2755 
2756  ConstHostCollection collection;
2757  return (impl_->delStatement(MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
2758  }
2759 
2760  // v6
2761  ConstHostPtr host = get6(subnet_id, addr);
2762  if (!host) {
2763  return (false);
2764  }
2765 
2766  // Ok, there is a host. Let's delete it.
2767  return del6(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
2768  host->getIdentifier().size());
2769 }
2770 
2771 bool
2773  const Host::IdentifierType& identifier_type,
2774  const uint8_t* identifier_begin, const size_t identifier_len) {
2775  // If operating in read-only mode, throw exception.
2776  impl_->checkReadOnly();
2777 
2778  // Set up the WHERE clause value
2779  MYSQL_BIND inbind[3];
2780 
2781  // subnet-id
2782  memset(inbind, 0, sizeof(inbind));
2783  uint32_t subnet = static_cast<uint32_t>(subnet_id);
2784  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2785  inbind[0].buffer = reinterpret_cast<char*>(&subnet);
2786  inbind[0].is_unsigned = MLM_TRUE;
2787 
2788  // identifier type
2789  char identifier_type_copy = static_cast<char>(identifier_type);
2790  inbind[1].buffer_type = MYSQL_TYPE_TINY;
2791  inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
2792  inbind[1].is_unsigned = MLM_TRUE;
2793 
2794  // identifier value
2795  std::vector<char> identifier_vec(identifier_begin,
2796  identifier_begin + identifier_len);
2797  unsigned long length = identifier_vec.size();
2798  inbind[2].buffer_type = MYSQL_TYPE_BLOB;
2799  inbind[2].buffer = &identifier_vec[0];
2800  inbind[2].buffer_length = length;
2801  inbind[2].length = &length;
2802 
2803  ConstHostCollection collection;
2805 }
2806 
2807 bool
2809  const Host::IdentifierType& identifier_type,
2810  const uint8_t* identifier_begin, const size_t identifier_len) {
2811  // If operating in read-only mode, throw exception.
2812  impl_->checkReadOnly();
2813 
2814  // Set up the WHERE clause value
2815  MYSQL_BIND inbind[3];
2816 
2817  // subnet-id
2818  memset(inbind, 0, sizeof(inbind));
2819  uint32_t subnet = static_cast<uint32_t>(subnet_id);
2820  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2821  inbind[0].buffer = reinterpret_cast<char*>(&subnet);
2822  inbind[0].is_unsigned = MLM_TRUE;
2823 
2824  // identifier type
2825  char identifier_type_copy = static_cast<char>(identifier_type);
2826  inbind[1].buffer_type = MYSQL_TYPE_TINY;
2827  inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
2828  inbind[1].is_unsigned = MLM_TRUE;
2829 
2830  // identifier value
2831  std::vector<char> identifier_vec(identifier_begin,
2832  identifier_begin + identifier_len);
2833  unsigned long length = identifier_vec.size();
2834  inbind[2].buffer_type = MYSQL_TYPE_BLOB;
2835  inbind[2].buffer = &identifier_vec[0];
2836  inbind[2].buffer_length = length;
2837  inbind[2].length = &length;
2838 
2839  ConstHostCollection collection;
2841 }
2842 
2845  const uint8_t* identifier_begin,
2846  const size_t identifier_len) const {
2847  // Set up the WHERE clause value
2848  MYSQL_BIND inbind[2];
2849  memset(inbind, 0, sizeof(inbind));
2850 
2851  // Identifier type.
2852  char identifier_type_copy = static_cast<char>(identifier_type);
2853  inbind[1].buffer = &identifier_type_copy;
2854  inbind[1].buffer_type = MYSQL_TYPE_TINY;
2855  inbind[1].is_unsigned = MLM_TRUE;
2856 
2857  // Identifier value.
2858  std::vector<char> identifier_vec(identifier_begin,
2859  identifier_begin + identifier_len);
2860  unsigned long int length = identifier_vec.size();
2861  inbind[0].buffer_type = MYSQL_TYPE_BLOB;
2862  inbind[0].buffer = &identifier_vec[0];
2863  inbind[0].buffer_length = length;
2864  inbind[0].length = &length;
2865 
2866  ConstHostCollection result;
2868  impl_->host_ipv46_exchange_,
2869  result, false);
2870  return (result);
2871 }
2872 
2875 
2876  // Set up the WHERE clause value
2877  MYSQL_BIND inbind[1];
2878  memset(inbind, 0, sizeof(inbind));
2879 
2880  uint32_t addr4 = address.toUint32();
2881  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2882  inbind[0].buffer = reinterpret_cast<char*>(&addr4);
2883  inbind[0].is_unsigned = MLM_TRUE;
2884 
2885  ConstHostCollection result;
2887  impl_->host_exchange_, result, false);
2888 
2889  return (result);
2890 }
2891 
2894  const Host::IdentifierType& identifier_type,
2895  const uint8_t* identifier_begin,
2896  const size_t identifier_len) const {
2897 
2898  return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
2900  impl_->host_exchange_));
2901 }
2902 
2905  const asiolink::IOAddress& address) const {
2906  // Check that address is IPv4, not IPv6.
2907  if (!address.isV4()) {
2908  isc_throw(BadValue, "MySqlHostDataSource::get4(2): wrong address type, "
2909  "address supplied is not an IPv4 address");
2910  }
2911 
2912  // Set up the WHERE clause value
2913  MYSQL_BIND inbind[2];
2914  uint32_t subnet = subnet_id;
2915  memset(inbind, 0, sizeof(inbind));
2916  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2917  inbind[0].buffer = reinterpret_cast<char*>(&subnet);
2918  inbind[0].is_unsigned = MLM_TRUE;
2919 
2920  uint32_t addr4 = address.toUint32();
2921  inbind[1].buffer_type = MYSQL_TYPE_LONG;
2922  inbind[1].buffer = reinterpret_cast<char*>(&addr4);
2923  inbind[1].is_unsigned = MLM_TRUE;
2924 
2925  ConstHostCollection collection;
2927  inbind, impl_->host_exchange_, collection, true);
2928 
2929  // Return single record if present, else clear the host.
2930  ConstHostPtr result;
2931  if (!collection.empty())
2932  result = *collection.begin();
2933 
2934  return (result);
2935 }
2936 
2939  const Host::IdentifierType& identifier_type,
2940  const uint8_t* identifier_begin,
2941  const size_t identifier_len) const {
2942 
2943  return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
2945  impl_->host_ipv6_exchange_));
2946 }
2947 
2950  const uint8_t prefix_len) const {
2952 
2953  // Set up the WHERE clause value
2954  MYSQL_BIND inbind[2];
2955  memset(inbind, 0, sizeof(inbind));
2956 
2957  std::string addr6 = prefix.toText();
2958  unsigned long addr6_length = addr6.size();
2959 
2960  inbind[0].buffer_type = MYSQL_TYPE_BLOB;
2961  inbind[0].buffer = reinterpret_cast<char*>
2962  (const_cast<char*>(addr6.c_str()));
2963  inbind[0].length = &addr6_length;
2964  inbind[0].buffer_length = addr6_length;
2965 
2966  uint8_t tmp = prefix_len;
2967  inbind[1].buffer_type = MYSQL_TYPE_TINY;
2968  inbind[1].buffer = reinterpret_cast<char*>(&tmp);
2969  inbind[1].is_unsigned = MLM_TRUE;
2970 
2971  ConstHostCollection collection;
2973  inbind, impl_->host_ipv6_exchange_,
2974  collection, true);
2975 
2976  // Return single record if present, else clear the host.
2977  ConstHostPtr result;
2978  if (!collection.empty()) {
2979  result = *collection.begin();
2980  }
2981 
2982  return (result);
2983 }
2984 
2987  const asiolink::IOAddress& address) const {
2988  // Set up the WHERE clause value
2989  MYSQL_BIND inbind[2];
2990  memset(inbind, 0, sizeof(inbind));
2991 
2992  uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
2993  inbind[0].buffer_type = MYSQL_TYPE_LONG;
2994  inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
2995  inbind[0].is_unsigned = MLM_TRUE;
2996 
2997  std::string addr6 = address.toText();
2998  unsigned long addr6_length = addr6.size();
2999 
3000  inbind[1].buffer_type = MYSQL_TYPE_BLOB;
3001  inbind[1].buffer = reinterpret_cast<char*>
3002  (const_cast<char*>(addr6.c_str()));
3003  inbind[1].length = &addr6_length;
3004  inbind[1].buffer_length = addr6_length;
3005 
3006  ConstHostCollection collection;
3008  inbind, impl_->host_ipv6_exchange_,
3009  collection, true);
3010 
3011  // Return single record if present, else clear the host.
3012  ConstHostPtr result;
3013  if (!collection.empty()) {
3014  result = *collection.begin();
3015  }
3016 
3017  return (result);
3018 }
3019 
3020 // Miscellaneous database methods.
3021 
3022 std::string MySqlHostDataSource::getName() const {
3023  std::string name = "";
3024  try {
3025  name = impl_->conn_.getParameter("name");
3026  } catch (...) {
3027  // Return an empty name
3028  }
3029  return (name);
3030 }
3031 
3033  return (std::string("Host data source that stores host information"
3034  "in MySQL database"));
3035 }
3036 
3037 std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const {
3038  return(impl_->getVersion());
3039 }
3040 
3041 void
3043  // If operating in read-only mode, throw exception.
3044  impl_->checkReadOnly();
3045  impl_->conn_.commit();
3046 }
3047 
3048 void
3050  // If operating in read-only mode, throw exception.
3051  impl_->checkReadOnly();
3052  impl_->conn_.rollback();
3053 }
3054 
3055 }; // end of isc::dhcp namespace
3056 }; // end of isc namespace
std::vector< std::string > text_statements_
Raw text of statements.
boost::shared_ptr< MySqlHostWithOptionsExchange > host_exchange_
Pointer to the object representing an exchange which can be used to retrieve hosts and DHCPv4 options...
MySqlHolder mysql_
MySQL connection handle.
Option descriptor.
Definition: cfg_option.h:35
void addResv(const IPv6Resrv &resv, const HostID &id)
Inserts IPv6 Reservation into ipv6_reservation table.
virtual std::string getDescription() const
Returns description of the backend.
Simple class representing an optional value.
ConstHostPtr getHost(const SubnetID &subnet_id, const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len, StatementIndex stindex, boost::shared_ptr< MySqlHostExchange > exchange) const
Retrieves a host by subnet and client's unique identifier.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:497
Fetch and Release MySQL Results.
boost::shared_ptr< MySqlOptionExchange > host_option_exchange_
Pointer to an object representing an exchange which can be used to insert DHCPv4 or DHCPv6 option int...
void checkError(const int status, const StatementIndex index, const char *what) const
Check Error and Throw Exception.
data::ConstElementPtr getContext() const
Returns const pointer to the user context.
Definition: user_context.h:24
virtual void rollback()
Rollback Transactions.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
uint8_t getPrefixLen() const
Returns prefix length.
Definition: host.h:140
virtual bool del4(const SubnetID &subnet_id, const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len)
Attempts to delete a host by (subnet4-id, identifier type, identifier)
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition: data.h:43
Data is truncated.
Definition: db_exceptions.h:35
boost::shared_ptr< Host > HostPtr
Pointer to the Host object.
Definition: host.h:725
void commit()
Commits transaction.
boost::shared_ptr< const CfgOption > ConstCfgOptionPtr
Const pointer.
Definition: cfg_option.h:500
#define DHCP6_OPTION_SPACE
Definition: option_space.h:17
virtual bool del6(const SubnetID &subnet_id, const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len)
Attempts to delete a host by (subnet6-id, identifier type, identifier)
boost::shared_ptr< Option > OptionPtr
Definition: option.h:37
const uint32_t MYSQL_SCHEMA_VERSION_MAJOR
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:67
void checkReadOnly() const
Throws exception if database is read only.
const uint32_t MYSQL_SCHEMA_VERSION_MINOR
IPv6 reservation for a host.
Definition: host.h:106
std::pair< uint32_t, uint32_t > getVersion() const
Returns backend version.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
boost::shared_ptr< MySqlHostIPv6Exchange > host_ipv6_exchange_
Pointer to an object representing an exchange which can be used to retrieve hosts,...
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:25
Exception thrown on failure to open database.
TaggedStatementArray tagged_statements
Prepared MySQL statements used by the backend to insert and retrieve hosts from the database.
Multiple lease records found where one expected.
Definition: db_exceptions.h:28
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
std::string formatted_value_
Option value in textual (CSV) format.
Definition: cfg_option.h:59
Definition: edns.h:19
const my_bool MLM_FALSE
MySQL false value.
std::pair< IPv6ResrvIterator, IPv6ResrvIterator > IPv6ResrvRange
Definition: host.h:188
const int DHCPSRV_DBG_TRACE_DETAIL
Additional information.
Definition: dhcpsrv_log.h:38
std::vector< ConstHostPtr > ConstHostCollection
Collection of the const Host objects.
Definition: host.h:731
MySqlConnection conn_
MySQL connection.
bool persistent_
Persistence flag.
Definition: cfg_option.h:44
A generic exception that is thrown when an unexpected error condition occurs.
virtual void commit()
Commit Transactions.
virtual ~MySqlHostDataSource()
Virtual destructor.
boost::shared_ptr< MySqlIPv6ReservationExchange > host_ipv6_reservation_exchange_
Pointer to an object representing an exchange which can be used to insert new IPv6 reservation.
void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex, std::vector< MYSQL_BIND > &bind)
Executes statements which inserts a row into one of the tables.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
boost::shared_ptr< const Host > ConstHostPtr
Const pointer to the Host object.
Definition: host.h:728
virtual std::string getName() const
Returns backend name.
std::string getParameter(const std::string &name) const
Returns value of a connection parameter.
Represents a device with IPv4 and/or IPv6 reservations.
Definition: host.h:242
bool configuredReadOnly() const
Convenience method checking if database should be opened with read only access.
Type
Type of the reservation.
Definition: host.h:112
#define DHCP4_OPTION_SPACE
Definition: option_space.h:16
virtual ConstHostPtr get6(const SubnetID &subnet_id, const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len) const
Returns a host connected to the IPv6 subnet.
IPv6ResrvCollection::const_iterator IPv6ResrvIterator
Definition: host.h:186
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
OptionPtr option_
Option instance.
Definition: cfg_option.h:38
const int MLM_MYSQL_FETCH_SUCCESS
MySQL fetch success code.
bool is_readonly_
Indicates if the database is opened in read only mode.
Authentication keys.
Definition: host.h:34
Defines the logger used by the top-level component of kea-dhcp-ddns.
uint16_t code_
Type getType() const
Returns reservation type.
Definition: host.h:149
virtual std::pair< uint32_t, uint32_t > getVersion() const
Returns backend version.
Implementation of the MySqlHostDataSource.
void getHostCollection(StatementIndex stindex, MYSQL_BIND *bind, boost::shared_ptr< MySqlHostExchange > exchange, ConstHostCollection &result, bool single) const
Creates collection of Host objects with associated information such as IPv6 reservations and/or DHCP ...
void addOptions(const StatementIndex &stindex, const ConstCfgOptionPtr &options_cfg, const uint64_t host_id)
Inserts multiple options into the database.
std::vector< MYSQL_STMT * > statements_
Prepared statements.
const int MLM_MYSQL_FETCH_FAILURE
MySQL fetch failure code.
boost::shared_ptr< MySqlHostIPv6Exchange > host_ipv46_exchange_
Pointer to an object representing an exchange which can be used to retrieve hosts,...
RAII object representing MySQL transaction.
bool isSpecified() const
Checks if the value is specified or unspecified.
boost::shared_ptr< OptionContainer > OptionContainerPtr
Pointer to the OptionContainer object.
Definition: cfg_option.h:206
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
Attempt to modify data in read-only database.
Definition: db_exceptions.h:49
void rollback()
Rollback Transactions.
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
virtual ConstHostCollection getAll(const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len) const
Return all hosts connected to any subnet for which reservations have been made using a specified iden...
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition: dhcpsrv_log.h:56
void commit()
Commit Transactions.
IdentifierType
Type of the host identifier.
Definition: host.h:252
bool delStatement(StatementIndex stindex, MYSQL_BIND *bind)
Executes statements that delete records.
void checkError(const int status, const StatementIndex &index, const char *what) const
Check Error and Throw Exception.
static const StatementIndex WRITE_STMTS_BEGIN
Index of first statement performing write to the database.
void prepareStatements(const TaggedStatement *start_statement, const TaggedStatement *end_statement)
Prepare statements.
void openDatabase()
Open Database.
uint64_t HostID
HostID (used only when storing in MySQL, PostgreSQL or Cassandra)
Definition: host.h:28
virtual ConstHostPtr get4(const SubnetID &subnet_id, const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len) const
Returns a host connected to the IPv4 subnet.
virtual ConstHostCollection getAll4(const asiolink::IOAddress &address) const
Returns a collection of hosts using the specified IPv4 address.
const asiolink::IOAddress & getPrefix() const
Returns prefix for the reservation.
Definition: host.h:135
virtual bool del(const SubnetID &subnet_id, const asiolink::IOAddress &addr)
Attempts to delete a host by (subnet-id, address)
Exception thrown on failure to execute a database function.
const my_bool MLM_TRUE
MySQL true value.
MySqlHostDataSource(const db::DatabaseConnection::ParameterMap &parameters)
Constructor.
boost::array< TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS > TaggedStatementArray
Array of tagged statements.
Database duplicate entry error.
Definition: db_exceptions.h:42
virtual void add(const HostPtr &host)
Adds a new host to the collection.
void addOption(const MySqlHostDataSourceImpl::StatementIndex &stindex, const OptionDescriptor &opt_desc, const std::string &opt_space, const OptionalValue< SubnetID > &subnet_id, const HostID &host_id)
Inserts a single DHCP option into the database.
uint32_t SubnetID
Unique identifier for a subnet (both v4 and v6)
Definition: lease.h:24
Common MySQL Connector Pool.