Kea 1.9.11
pgsql_host_data_source.cc
Go to the documentation of this file.
1// Copyright (C) 2016-2021 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
11#include <dhcp/libdhcp++.h>
12#include <dhcp/option.h>
14#include <dhcp/option_space.h>
16#include <dhcpsrv/cfg_option.h>
17#include <dhcpsrv/cfgmgr.h>
18#include <dhcpsrv/dhcpsrv_log.h>
19#include <dhcpsrv/host_mgr.h>
21#include <dhcpsrv/timer_mgr.h>
22#include <util/buffer.h>
24#include <util/optional.h>
25
26#include <boost/algorithm/string/split.hpp>
27#include <boost/algorithm/string/classification.hpp>
28#include <boost/array.hpp>
29#include <boost/pointer_cast.hpp>
30#include <boost/static_assert.hpp>
31
32#include <stdint.h>
33
34#include <mutex>
35#include <string>
36
37using namespace isc;
38using namespace isc::asiolink;
39using namespace isc::db;
40using namespace isc::dhcp;
41using namespace isc::util;
42using namespace isc::data;
43using namespace std;
44
45namespace {
46
50const size_t OPTION_VALUE_MAX_LEN = 4096;
51
56const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
57
59const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
60
87class PgSqlHostExchange : public PgSqlExchange {
88private:
89
94 static const int HOST_ID_COL = 0;
95 static const int DHCP_IDENTIFIER_COL = 1;
96 static const int DHCP_IDENTIFIER_TYPE_COL = 2;
97 static const int DHCP4_SUBNET_ID_COL = 3;
98 static const int DHCP6_SUBNET_ID_COL = 4;
99 static const int IPV4_ADDRESS_COL = 5;
100 static const int HOSTNAME_COL = 6;
101 static const int DHCP4_CLIENT_CLASSES_COL = 7;
102 static const int DHCP6_CLIENT_CLASSES_COL = 8;
103 static const int USER_CONTEXT_COL = 9;
104 static const int DHCP4_NEXT_SERVER_COL = 10;
105 static const int DHCP4_SERVER_HOSTNAME_COL = 11;
106 static const int DHCP4_BOOT_FILE_NAME_COL = 12;
107 static const int AUTH_KEY_COL = 13;
109 static const size_t HOST_COLUMNS = 14;
110
111public:
112
119 PgSqlHostExchange(const size_t additional_columns_num = 0)
120 : PgSqlExchange(HOST_COLUMNS + additional_columns_num) {
121 // Set the column names for use by this class. This only comprises
122 // names used by the PgSqlHostExchange class. Derived classes will
123 // need to set names for the columns they use. Currently these are
124 // only used for logging purposes.
125 columns_[HOST_ID_COL] = "host_id";
126 columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
127 columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
128 columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
129 columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
130 columns_[IPV4_ADDRESS_COL] = "ipv4_address";
131 columns_[HOSTNAME_COL] = "hostname";
132 columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
133 columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
134 columns_[USER_CONTEXT_COL] = "user_context";
135 columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
136 columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
137 columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
138 columns_[AUTH_KEY_COL] = "auth_key";
139
140 BOOST_STATIC_ASSERT(12 < HOST_COLUMNS);
141 };
142
144 virtual ~PgSqlHostExchange() {
145 }
146
152 virtual void clear() {
153 host_.reset();
154 };
155
170 size_t findAvailColumn() const {
171 std::vector<std::string>::const_iterator empty_column =
172 std::find(columns_.begin(), columns_.end(), std::string());
173 return (std::distance(columns_.begin(), empty_column));
174 }
175
180 HostID getHostId(const PgSqlResult& r, int row) {
181 HostID host_id;
182 getColumnValue(r, row, HOST_ID_COL, host_id);
183 return (host_id);
184 }
185
201 PsqlBindArrayPtr createBindForSend(const HostPtr& host, const bool unique_ip) {
202 if (!host) {
203 isc_throw(BadValue, "createBindForSend:: host object is NULL");
204 }
205
206 // Store the host to ensure bound values remain in scope
207 host_ = host;
208
209 // Bind the host data to the array
210 PsqlBindArrayPtr bind_array(new PsqlBindArray());
211 try {
212 // host_id : is auto_incremented skip it
213
214 // dhcp_identifier : BYTEA NOT NULL
215 bind_array->add(host->getIdentifier());
216
217 // dhcp_identifier_type : SMALLINT NOT NULL
218 bind_array->add(host->getIdentifierType());
219
220 // dhcp4_subnet_id : INT NULL
221 if (host->getIPv4SubnetID() == SUBNET_ID_UNUSED) {
222 bind_array->addNull();
223 }
224 else {
225 bind_array->add(host->getIPv4SubnetID());
226 }
227
228 // dhcp6_subnet_id : INT NULL
229 if (host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
230 bind_array->addNull();
231 }
232 else {
233 bind_array->add(host->getIPv6SubnetID());
234 }
235
236 // ipv4_address : BIGINT NULL
237 bind_array->add((host->getIPv4Reservation()));
238
239 // hostname : VARCHAR(255) NULL
240 bind_array->add(host->getHostname());
241
242 // dhcp4_client_classes : VARCHAR(255) NULL
243 // Override default separator to not include space after comma.
244 bind_array->addTempString(host->getClientClasses4().toText(","));
245
246 // dhcp6_client_classes : VARCHAR(255) NULL
247 bind_array->addTempString(host->getClientClasses6().toText(","));
248
249 // user_context: TEXT NULL
250 ConstElementPtr ctx = host->getContext();
251 if (ctx) {
252 std::string user_context_ = ctx->str();
253 bind_array->addTempString(user_context_);
254 } else {
255 bind_array->addNull();
256 }
257
258 // dhcp4_next_server : BIGINT NULL
259 bind_array->add((host->getNextServer()));
260
261 // dhcp4_server_hostname : VARCHAR(64)
262 bind_array->add(host->getServerHostname());
263
264 // dhcp4_boot_file_name : VARCHAR(128)
265 bind_array->add(host->getBootFileName());
266
267 // add auth keys
268 std::string key = host->getKey().toText();
269 if (key.empty()) {
270 bind_array->addNull();
271 } else {
272 bind_array->add(key);
273 }
274
275 // When checking whether the IP is unique we need to bind the IPv4 address
276 // at the end of the query as it has additional binding for the IPv4
277 // address.
278 if (unique_ip) {
279 bind_array->add(host->getIPv4Reservation()); // ipv4_address
280 bind_array->add(host->getIPv4SubnetID()); // subnet_id
281 }
282
283
284 } catch (const std::exception& ex) {
285 host_.reset();
287 "Could not create bind array from Host: "
288 << host->getHostname() << ", reason: " << ex.what());
289 }
290
291 return (bind_array);
292 };
293
307 virtual void processRowData(ConstHostCollection& hosts,
308 const PgSqlResult& r, int row) {
309 // Peek at the host id , so we can skip it if we already have it
310 // This lets us avoid constructing a copy of host for each
311 // of its sub-rows (options, etc...)
312 HostID row_host_id = getHostId(r, row);
313
314 // Add new host only if there are no hosts or the host id of the
315 // most recently added host is different than the host id of the
316 // currently processed host.
317 if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
318 HostPtr host = retrieveHost(r, row, row_host_id);
319 hosts.push_back(host);
320 }
321 }
322
334 HostPtr retrieveHost(const PgSqlResult& r, int row,
335 const HostID& peeked_host_id = 0) {
336
337 // If the caller peeked ahead at the host_id use that, otherwise
338 // read it from the row.
339 HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
340
341 // dhcp_identifier : BYTEA NOT NULL
342 uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
343 size_t identifier_len;
344 convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
345 sizeof(identifier_value), identifier_len);
346
347 // dhcp_identifier_type : SMALLINT NOT NULL
348 uint8_t type;
349 getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
350 if (type > MAX_IDENTIFIER_TYPE) {
351 isc_throw(BadValue, "invalid dhcp identifier type returned: "
352 << static_cast<int>(type));
353 }
354
355 Host::IdentifierType identifier_type =
356 static_cast<Host::IdentifierType>(type);
357
358 // dhcp4_subnet_id : INT NULL
359 uint32_t subnet_id(SUBNET_ID_UNUSED);
360 if (!isColumnNull(r, row, DHCP4_SUBNET_ID_COL)) {
361 getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
362 }
363 SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
364
365 // dhcp6_subnet_id : INT NULL
366 subnet_id = SUBNET_ID_UNUSED;
367 if (!isColumnNull(r, row, DHCP6_SUBNET_ID_COL)) {
368 getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
369 }
370 SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
371
372 // ipv4_address : BIGINT NULL
373 uint32_t addr4(0);
374 if (!isColumnNull(r, row, IPV4_ADDRESS_COL)) {
375 getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
376 }
377 isc::asiolink::IOAddress ipv4_reservation(addr4);
378
379 // hostname : VARCHAR(255) NULL
380 std::string hostname;
381 if (!isColumnNull(r, row, HOSTNAME_COL)) {
382 getColumnValue(r, row, HOSTNAME_COL, hostname);
383 }
384
385 // dhcp4_client_classes : VARCHAR(255) NULL
386 std::string dhcp4_client_classes;
387 if (!isColumnNull(r, row, DHCP4_CLIENT_CLASSES_COL)) {
388 getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
389 }
390
391 // dhcp6_client_classes : VARCHAR(255) NULL
392 std::string dhcp6_client_classes;
393 if (!isColumnNull(r, row, DHCP6_CLIENT_CLASSES_COL)) {
394 getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
395 }
396
397 // user_context: TEXT
398 std::string user_context;
399 if (!isColumnNull(r, row, USER_CONTEXT_COL)) {
400 getColumnValue(r, row, USER_CONTEXT_COL, user_context);
401 }
402
403 // dhcp4_next_server : BIGINT NULL
404 uint32_t dhcp4_next_server_as_uint32(0);
405 if (!isColumnNull(r, row, DHCP4_NEXT_SERVER_COL)) {
406 getColumnValue(r, row, DHCP4_NEXT_SERVER_COL, dhcp4_next_server_as_uint32);
407 }
408 isc::asiolink::IOAddress dhcp4_next_server(dhcp4_next_server_as_uint32);
409
410 // dhcp4_server_hostname : VARCHAR(64)
411 std::string dhcp4_server_hostname;
412 if (!isColumnNull(r, row, DHCP4_SERVER_HOSTNAME_COL)) {
413 getColumnValue(r, row, DHCP4_SERVER_HOSTNAME_COL, dhcp4_server_hostname);
414 }
415
416 // dhcp4_boot_file_name : VARCHAR(128)
417 std::string dhcp4_boot_file_name;
418 if (!isColumnNull(r, row, DHCP4_BOOT_FILE_NAME_COL)) {
419 getColumnValue(r, row, DHCP4_BOOT_FILE_NAME_COL, dhcp4_boot_file_name);
420 }
421
422 // auth_key : VARCHAR(16)
423 std::string auth_key;
424 if (!isColumnNull(r, row, AUTH_KEY_COL)) {
425 getColumnValue(r, row, AUTH_KEY_COL, auth_key);
426 }
427
428 // Finally, attempt to create the new host.
429 HostPtr host;
430 try {
431 host.reset(new Host(identifier_value, identifier_len,
432 identifier_type, dhcp4_subnet_id,
433 dhcp6_subnet_id, ipv4_reservation, hostname,
434 dhcp4_client_classes, dhcp6_client_classes,
435 dhcp4_next_server, dhcp4_server_hostname,
436 dhcp4_boot_file_name, AuthKey(auth_key)));
437
438 // Set the user context if there is one.
439 if (!user_context.empty()) {
440 try {
441 ConstElementPtr ctx = Element::fromJSON(user_context);
442 if (!ctx || (ctx->getType() != Element::map)) {
443 isc_throw(BadValue, "user context '" << user_context
444 << "' is not a JSON map");
445 }
446 host->setContext(ctx);
447 } catch (const isc::data::JSONError& ex) {
448 isc_throw(BadValue, "user context '" << user_context
449 << "' is invalid JSON: " << ex.what());
450 }
451 }
452
453 host->setHostId(host_id);
454 } catch (const isc::Exception& ex) {
455 isc_throw(DbOperationError, "Could not create host: " << ex.what());
456 }
457
458 return(host);
459 };
460
461protected:
464 HostPtr host_;
465};
466
476class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
477private:
478
480 static const size_t OPTION_COLUMNS = 7;
481
496 class OptionProcessor {
497 public:
498
505 OptionProcessor(const Option::Universe& universe,
506 const size_t start_column)
507 : universe_(universe), start_column_(start_column),
508 option_id_index_(start_column), code_index_(start_column_ + 1),
509 value_index_(start_column_ + 2),
510 formatted_value_index_(start_column_ + 3),
511 space_index_(start_column_ + 4),
512 persistent_index_(start_column_ + 5),
513 user_context_index_(start_column_ + 6),
514 most_recent_option_id_(0) {
515 }
516
521 void clear() {
522 most_recent_option_id_ = 0;
523 }
524
557 void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
558 int row) {
559 // If the option id on this row is NULL, then there's no
560 // option of this type (4/6) on this row to fetch, so bail.
561 if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
562 return;
563 }
564
565 // option_id: INT
566 uint64_t option_id;
567 PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);
568
569 // The row option id must be greater than id if the most recent
570 // option because they are ordered by option id. Otherwise
571 // we assume that we have already processed this option.
572 if (most_recent_option_id_ >= option_id) {
573 return;
574 }
575
576 // Remember current option id as the most recent processed one. We
577 // will be comparing it with option ids in subsequent rows.
578 most_recent_option_id_ = option_id;
579
580 // code: SMALLINT NOT NULL
581 uint16_t code;
582 PgSqlExchange::getColumnValue(r, row, code_index_, code);
583
584 // value: BYTEA
585 uint8_t value[OPTION_VALUE_MAX_LEN];
586 size_t value_len(0);
587 if (!isColumnNull(r, row, value_index_)) {
588 PgSqlExchange::convertFromBytea(r, row, value_index_, value,
589 sizeof(value), value_len);
590 }
591
592 // formatted_value: TEXT
593 std::string formatted_value;
594 if (!isColumnNull(r, row, formatted_value_index_)) {
595 PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
596 formatted_value);
597 }
598
599 // space: VARCHAR(128)
600 std::string space;
601 if (!isColumnNull(r, row, space_index_)) {
602 PgSqlExchange::getColumnValue(r, row, space_index_, space);
603 }
604
605 // If empty or null space provided, use a default top level space.
606 if (space.empty()) {
607 space = (universe_ == Option::V4 ?
609 }
610
611 // persistent: BOOL default false
612 bool persistent;
613 PgSqlExchange::getColumnValue(r, row, persistent_index_,
614 persistent);
615
616 // user_context: TEXT
617 std::string user_context;
618 if (!isColumnNull(r, row, user_context_index_)) {
619 PgSqlExchange::getColumnValue(r, row, user_context_index_,
620 user_context);
621 }
622
623 // Options are held in a binary or textual format in the database.
624 // This is similar to having an option specified in a server
625 // configuration file. Such option is converted to appropriate C++
626 // class, using option definition. Thus, we need to find the
627 // option definition for this option code and option space.
628
629 // If the option space is a standard DHCPv4 or DHCPv6 option space,
630 // this is most likely a standard option, for which we have a
631 // definition created within libdhcp++.
632 OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
633
634 // Otherwise, we may check if this an option encapsulated within the
635 // vendor space.
636 if (!def && (space != DHCP4_OPTION_SPACE) &&
637 (space != DHCP6_OPTION_SPACE)) {
638 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
639 if (vendor_id > 0) {
640 def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
641 code);
642 }
643 }
644
645 // In all other cases, we use runtime option definitions, which
646 // should be also registered within the libdhcp++.
647 if (!def) {
648 def = LibDHCP::getRuntimeOptionDef(space, code);
649 }
650
651 OptionPtr option;
652
653 if (!def) {
654 // If no definition found, we use generic option type.
655 OptionBuffer buf(value, value + value_len);
656 option.reset(new Option(universe_, code, buf.begin(),
657 buf.end()));
658 } else {
659 // The option value may be specified in textual or binary format
660 // in the database. If formatted_value is empty, the binary
661 // format is used. Depending on the format we use a different
662 // variant of the optionFactory function.
663 if (formatted_value.empty()) {
664 OptionBuffer buf(value, value + value_len);
665 option = def->optionFactory(universe_, code, buf.begin(),
666 buf.end());
667 } else {
668 // Spit the value specified in comma separated values
669 // format.
670 std::vector<std::string> split_vec;
671 boost::split(split_vec, formatted_value,
672 boost::is_any_of(","));
673 option = def->optionFactory(universe_, code, split_vec);
674 }
675 }
676
677 OptionDescriptor desc(option, persistent, formatted_value);
678
679 // Set the user context if there is one into the option descriptor.
680 if (!user_context.empty()) {
681 try {
682 ConstElementPtr ctx = Element::fromJSON(user_context);
683 if (!ctx || (ctx->getType() != Element::map)) {
684 isc_throw(BadValue, "user context '" << user_context
685 << "' is no a JSON map");
686 }
687 desc.setContext(ctx);
688 } catch (const isc::data::JSONError& ex) {
689 isc_throw(BadValue, "user context '" << user_context
690 << "' is invalid JSON: " << ex.what());
691 }
692 }
693
694 cfg->add(desc, space);
695 }
696
701 void setColumnNames(std::vector<std::string>& columns) {
702 columns[option_id_index_] = "option_id";
703 columns[code_index_] = "code";
704 columns[value_index_] = "value";
705 columns[formatted_value_index_] = "formatted_value";
706 columns[space_index_] = "space";
707 columns[persistent_index_] = "persistent";
708 columns[user_context_index_] = "user_context";
709 }
710
711 private:
713 Option::Universe universe_;
714
716 size_t start_column_;
717
719
721
722
723 size_t option_id_index_;
724
726 size_t code_index_;
727
729 size_t value_index_;
730
732 size_t formatted_value_index_;
733
735 size_t space_index_;
736
738 size_t persistent_index_;
740
742 size_t user_context_index_;
743
745 uint64_t most_recent_option_id_;
746 };
747
749 typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
750
751public:
752
759 enum FetchedOptions {
760 DHCP4_ONLY,
761 DHCP6_ONLY,
762 DHCP4_AND_DHCP6
763 };
764
773 PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
774 const size_t additional_columns_num = 0)
775 : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
776 + additional_columns_num),
777 opt_proc4_(), opt_proc6_() {
778
779 // Create option processor for DHCPv4 options, if required.
780 if ((fetched_options == DHCP4_ONLY) ||
781 (fetched_options == DHCP4_AND_DHCP6)) {
782 opt_proc4_.reset(new OptionProcessor(Option::V4,
783 findAvailColumn()));
784 opt_proc4_->setColumnNames(columns_);
785 }
786
787 // Create option processor for DHCPv6 options, if required.
788 if ((fetched_options == DHCP6_ONLY) ||
789 (fetched_options == DHCP4_AND_DHCP6)) {
790 opt_proc6_.reset(new OptionProcessor(Option::V6,
791 findAvailColumn()));
792 opt_proc6_->setColumnNames(columns_);
793 }
794 }
795
801 virtual void clear() {
802 PgSqlHostExchange::clear();
803 if (opt_proc4_) {
804 opt_proc4_->clear();
805 }
806
807 if (opt_proc6_) {
808 opt_proc6_->clear();
809 }
810 }
811
821 virtual void processRowData(ConstHostCollection& hosts,
822 const PgSqlResult& r, int row) {
823 HostPtr current_host;
824 if (hosts.empty()) {
825 // Must be the first one, fetch it.
826 current_host = retrieveHost(r, row);
827 hosts.push_back(current_host);
828 } else {
829 // Peek at the host id so we can skip it if we already have
830 // this host. This lets us avoid retrieving the host needlessly
831 // for each of its sub-rows (options, etc...).
832 HostID row_host_id = getHostId(r, row);
833 current_host = boost::const_pointer_cast<Host>(hosts.back());
834
835 // if the row's host id is greater than the one we've been
836 // working on we're starting a new host, so fetch it.
837 if (row_host_id > current_host->getHostId()) {
838 current_host = retrieveHost(r, row, row_host_id);
839 hosts.push_back(current_host);
840 }
841 }
842
843 // Parse DHCPv4 options if required to do so.
844 if (opt_proc4_) {
845 CfgOptionPtr cfg = current_host->getCfgOption4();
846 opt_proc4_->retrieveOption(cfg, r, row);
847 }
848
849 // Parse DHCPv6 options if required to do so.
850 if (opt_proc6_) {
851 CfgOptionPtr cfg = current_host->getCfgOption6();
852 opt_proc6_->retrieveOption(cfg, r, row);
853 }
854 }
855
856private:
857
869 static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
870 return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
871 OPTION_COLUMNS);
872 }
873
877 OptionProcessorPtr opt_proc4_;
878
882 OptionProcessorPtr opt_proc6_;
883};
884
897class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
898private:
899
901 static const size_t RESERVATION_COLUMNS = 5;
902
903public:
904
909 PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
910 : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
911 reservation_id_index_(findAvailColumn()),
912 address_index_(reservation_id_index_ + 1),
913 prefix_len_index_(reservation_id_index_ + 2),
914 type_index_(reservation_id_index_ + 3),
915 iaid_index_(reservation_id_index_ + 4),
916 most_recent_reservation_id_(0) {
917
918 // Provide names of additional columns returned by the queries.
919 columns_[reservation_id_index_] = "reservation_id";
920 columns_[address_index_] = "address";
921 columns_[prefix_len_index_] = "prefix_len";
922 columns_[type_index_] = "type";
923 columns_[iaid_index_] = "dhcp6_iaid";
924
925 BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
926 }
927
933 void clear() {
934 PgSqlHostWithOptionsExchange::clear();
935 most_recent_reservation_id_ = 0;
936 }
937
941 uint64_t getReservationId(const PgSqlResult& r, int row) const {
942 uint64_t resv_id = 0;
943 if (!isColumnNull(r, row, reservation_id_index_)) {
944 getColumnValue(r, row, reservation_id_index_, resv_id);
945 }
946
947 return (resv_id);
948 };
949
954 IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
955
956 // type: SMALLINT NOT NULL
957 uint16_t tmp;
958 getColumnValue(r, row, type_index_, tmp);
959
960 // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
961 IPv6Resrv::Type resv_type;
962 switch (tmp) {
963 case 0:
964 resv_type = IPv6Resrv::TYPE_NA;
965 break;
966
967 case 2:
968 resv_type = IPv6Resrv::TYPE_PD;
969 break;
970
971 default:
973 "invalid IPv6 reservation type returned: "
974 << tmp << ". Only 0 or 2 are allowed.");
975 }
976
977 // address VARCHAR(39) NOT NULL
978 isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));
979
980 // prefix_len: SMALLINT NOT NULL
981 uint16_t prefix_len;
982 getColumnValue(r, row, prefix_len_index_, prefix_len);
983
984 // @todo once we support populating iaid
985 // iaid: INT
986 // int iaid;
987 // getColumnValue(r, row, iaid_index_, iaid);
988
989 // Create the reservation.
990 IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
991 return (reservation);
992 };
993
1015 virtual void processRowData(ConstHostCollection& hosts,
1016 const PgSqlResult& r, int row) {
1017 // Call parent class to fetch host information and options.
1018 PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
1019
1020 // Shouldn't happen but just in case
1021 if (hosts.empty()) {
1022 isc_throw(Unexpected, "no host information while retrieving"
1023 " IPv6 reservation");
1024 }
1025
1026 // If we have reservation id we haven't seen yet, retrieve the
1027 // the reservation, adding it to the current host
1028 uint64_t reservation_id = getReservationId(r, row);
1029 if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
1030 HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
1031 host->addReservation(retrieveReservation(r, row));
1032 most_recent_reservation_id_ = reservation_id;
1033 }
1034 }
1035
1036private:
1038
1039
1040 size_t reservation_id_index_;
1041
1043 size_t address_index_;
1044
1046 size_t prefix_len_index_;
1047
1049 size_t type_index_;
1050
1052 size_t iaid_index_;
1053
1055
1057 uint64_t most_recent_reservation_id_;
1058};
1059
1070class PgSqlIPv6ReservationExchange : public PgSqlExchange {
1071private:
1072
1074 static const size_t RESRV_COLUMNS = 6;
1075
1076public:
1077
1081 PgSqlIPv6ReservationExchange()
1082 : PgSqlExchange(RESRV_COLUMNS),
1083 resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
1084 // Set the column names (for error messages)
1085 columns_[0] = "host_id";
1086 columns_[1] = "address";
1087 columns_[2] = "prefix_len";
1088 columns_[3] = "type";
1089 columns_[4] = "dhcp6_iaid";
1090 BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
1091 }
1092
1107 PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
1108 const HostID& host_id,
1109 const bool unique_ip) {
1110 // Store the values to ensure they remain valid.
1111 // Technically we don't need this, as currently all the values
1112 // are converted to strings and stored by the bind array.
1113 resv_ = resv;
1114
1115 PsqlBindArrayPtr bind_array(new PsqlBindArray());
1116
1117 try {
1118 // address VARCHAR(39) NOT NULL
1119 bind_array->add(resv.getPrefix());
1120
1121 // prefix_len: SMALLINT NOT NULL
1122 bind_array->add(resv.getPrefixLen());
1123
1124 // type: SMALLINT NOT NULL
1125 // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
1126 uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
1127 bind_array->add(type);
1128
1129 // dhcp6_iaid: INT UNSIGNED
1131 bind_array->addNull();
1132
1133 // host_id: BIGINT NOT NULL
1134 bind_array->add(host_id);
1135
1136 // When checking whether the IP is unique we need to bind the IPv6 address
1137 // and prefix length at the end of the query as it has additional binding
1138 // for the IPv6 address and prefix length.
1139 if (unique_ip) {
1140 bind_array->add(resv.getPrefix()); // address
1141 bind_array->add(resv.getPrefixLen()); // prefix_len
1142 }
1143 } catch (const std::exception& ex) {
1145 "Could not create bind array from IPv6 Reservation: "
1146 << resv_.toText() << ", reason: " << ex.what());
1147 }
1148
1149 return (bind_array);
1150 }
1151
1152private:
1154 IPv6Resrv resv_;
1155};
1156
1160class PgSqlOptionExchange : public PgSqlExchange {
1161private:
1162
1163 static const int OPTION_ID_COL = 0;
1164 static const int CODE_COL = 1;
1165 static const int VALUE_COL = 2;
1166 static const int FORMATTED_VALUE_COL = 3;
1167 static const int SPACE_COL = 4;
1168 static const int PERSISTENT_COL = 5;
1169 static const int USER_CONTEXT_COL = 6;
1170 static const int DHCP_CLIENT_CLASS_COL = 7;
1171 static const int DHCP_SUBNET_ID_COL = 8;
1172 static const int HOST_ID_COL = 9;
1173 static const int SCOPE_ID_COL = 10;
1174
1176 static const size_t OPTION_COLUMNS = 11;
1177
1178public:
1179
1181 PgSqlOptionExchange()
1182 : PgSqlExchange(OPTION_COLUMNS), value_(),
1183 value_len_(0), option_() {
1184 columns_[OPTION_ID_COL] = "option_id";
1185 columns_[CODE_COL] = "code";
1186 columns_[VALUE_COL] = "value";
1187 columns_[FORMATTED_VALUE_COL] = "formatted_value";
1188 columns_[SPACE_COL] = "space";
1189 columns_[PERSISTENT_COL] = "persistent";
1190 columns_[USER_CONTEXT_COL] = "user_context";
1191 columns_[DHCP_CLIENT_CLASS_COL] = "dhcp_client_class";
1192 columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
1193 columns_[HOST_ID_COL] = "host_id";
1194 columns_[SCOPE_ID_COL] = "scope_id";
1195
1196 BOOST_STATIC_ASSERT(10 < OPTION_COLUMNS);
1197 }
1198
1207 PsqlBindArrayPtr createBindForSend(const OptionDescriptor& opt_desc,
1208 const std::string& opt_space,
1209 const HostID& host_id) {
1210 // Hold pointer to the option to make sure it remains valid until
1211 // we complete a query.
1212 option_ = opt_desc.option_;
1213
1214 // Create the bind-array
1215 PsqlBindArrayPtr bind_array(new PsqlBindArray());
1216
1217 try {
1218 // option_id: is auto_incremented so skip it
1219
1220 // code: SMALLINT UNSIGNED NOT NULL
1221 bind_array->add(option_->getType());
1222
1223 // value: BYTEA NULL
1224 if (opt_desc.formatted_value_.empty() &&
1225 (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
1226 // The formatted_value is empty and the option value is
1227 // non-empty so we need to prepare on-wire format for the
1228 // option and store it in the database as a BYTEA.
1229 OutputBuffer buf(opt_desc.option_->len());
1230 opt_desc.option_->pack(buf);
1231 const char* buf_ptr = static_cast<const char*>(buf.getData());
1232 value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
1233 buf_ptr + buf.getLength());
1234 value_len_ = value_.size();
1235 bind_array->add(value_);
1236 } else {
1237 // No value or formatted_value specified. In this case, the
1238 // value BYTEA should be NULL.
1239 bind_array->addNull(PsqlBindArray::BINARY_FMT);
1240 }
1241
1242 // formatted_value: TEXT NULL,
1243 if (!opt_desc.formatted_value_.empty()) {
1244 bind_array->addTempString(opt_desc.formatted_value_);
1245 } else {
1246 bind_array->addNull();
1247 }
1248
1249 // space: VARCHAR(128) NULL
1250 if (!opt_space.empty()) {
1251 bind_array->addTempString(opt_space);
1252 } else {
1253 bind_array->addNull();
1254 }
1255
1256 // persistent: BOOLEAN DEFAULT false
1257 bind_array->add(opt_desc.persistent_);
1258
1259 // user_context: TEXT NULL,
1260 ConstElementPtr ctx = opt_desc.getContext();
1261 if (ctx) {
1262 std::string user_context_ = ctx->str();
1263 bind_array->addTempString(user_context_);
1264 } else {
1265 bind_array->addNull();
1266 }
1267
1268 // host_id: INT NULL
1269 if (!host_id) {
1270 isc_throw(BadValue, "host_id cannot be null");
1271 }
1272 bind_array->add(host_id);
1273
1274 } catch (const std::exception& ex) {
1276 "Could not create bind array for inserting DHCP "
1277 "host option: " << option_->toText() << ", reason: "
1278 << ex.what());
1279 }
1280
1281 return (bind_array);
1282 }
1283
1284private:
1285
1287 std::vector<uint8_t> value_;
1288
1290 size_t value_len_;
1291
1293 OptionPtr option_;
1294};
1295
1296} // namespace
1297
1298namespace isc {
1299namespace dhcp {
1300
1311public:
1312
1319 IOServiceAccessorPtr io_service_accessor,
1320 db::DbCallback db_reconnect_callback);
1321
1326
1329 boost::shared_ptr<PgSqlHostWithOptionsExchange> host_ipv4_exchange_;
1330
1333 boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
1334
1338 boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
1339
1342 boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
1343
1347 boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
1348
1351
1354};
1355
1363public:
1364
1366 std::vector<PgSqlHostContextPtr> pool_;
1367
1369 std::mutex mutex_;
1370};
1371
1373typedef boost::shared_ptr<PgSqlHostContextPool> PgSqlHostContextPoolPtr;
1374
1377public:
1378
1388 GET_HOST_DHCPID, // Gets hosts by host identifier
1389 GET_HOST_ADDR, // Gets hosts by IPv4 address
1390 GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
1391 GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
1392 GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
1393 GET_HOST_PREFIX, // Gets host by IPv6 prefix
1394 GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
1395 GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
1396 GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
1397 GET_HOST_HOSTNAME, // Gets hosts by hostname
1398 GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
1399 GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
1400 GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
1401 GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
1402 GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
1403 GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
1404 INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
1405 INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
1406 INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
1407 INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
1408 INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
1409 INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
1410 DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
1411 DEL_HOST_ADDR6, // Delete v6 host (subnet-id, addr6)
1412 DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
1413 DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
1414 NUM_STATEMENTS // Number of statements
1416
1423
1429
1432
1455 static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
1456
1467
1484 uint64_t addStatement(PgSqlHostContextPtr& ctx,
1486 PsqlBindArrayPtr& bind,
1487 const bool return_last_id = false);
1488
1498 PsqlBindArrayPtr& bind);
1499
1505 void addResv(PgSqlHostContextPtr& ctx,
1506 const IPv6Resrv& resv,
1507 const HostID& id);
1508
1520 const OptionDescriptor& opt_desc,
1521 const std::string& opt_space,
1522 const Optional<SubnetID>& subnet_id,
1523 const HostID& host_id);
1524
1534 const StatementIndex& stindex,
1535 const ConstCfgOptionPtr& options_cfg,
1536 const uint64_t host_id);
1537
1557 StatementIndex stindex,
1558 PsqlBindArrayPtr bind,
1559 boost::shared_ptr<PgSqlHostExchange> exchange,
1560 ConstHostCollection& result,
1561 bool single) const;
1562
1581 const SubnetID& subnet_id,
1582 const Host::IdentifierType& identifier_type,
1583 const uint8_t* identifier_begin,
1584 const size_t identifier_len,
1585 StatementIndex stindex,
1586 boost::shared_ptr<PgSqlHostExchange> exchange) const;
1587
1597 void checkReadOnly(PgSqlHostContextPtr& ctx) const;
1598
1607 std::pair<uint32_t, uint32_t> getVersion() const;
1608
1611
1615
1618
1622
1624 std::string timer_name_;
1625};
1626
1627namespace {
1628
1630typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS>
1631TaggedStatementArray;
1632
1635TaggedStatementArray tagged_statements = { {
1636 // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
1637 // Retrieves host information, IPv6 reservations and both DHCPv4 and
1638 // DHCPv6 options associated with the host. The LEFT JOIN clause is used
1639 // to retrieve information from 4 different tables using a single query.
1640 // Hence, this query returns multiple rows for a single host.
1641 {2,
1642 { OID_BYTEA, OID_INT2 },
1643 "get_host_dhcpid",
1644 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1645 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1646 " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1647 " h.user_context, "
1648 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1649 " h.dhcp4_boot_file_name, h.auth_key, "
1650 " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1651 " o4.persistent, o4.user_context, "
1652 " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1653 " o6.persistent, o6.user_context, "
1654 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1655 "FROM hosts AS h "
1656 "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1657 "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1658 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1659 "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
1660 "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1661 },
1662
1663 // PgSqlHostDataSourceImpl::GET_HOST_ADDR
1664 // Retrieves host information along with the DHCPv4 options associated with
1665 // it. Left joining the dhcp4_options table results in multiple rows being
1666 // returned for the same host. The host is retrieved by IPv4 address.
1667 {1,
1668 { OID_INT8 },
1669 "get_host_addr",
1670 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1671 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1672 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1673 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1674 " h.dhcp4_boot_file_name, h.auth_key, "
1675 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1676 " o.persistent, o.user_context "
1677 "FROM hosts AS h "
1678 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1679 "WHERE ipv4_address = $1 "
1680 "ORDER BY h.host_id, o.option_id"
1681 },
1682
1683 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
1684 // Retrieves host information and DHCPv4 options using subnet identifier
1685 // and client's identifier. Left joining the dhcp4_options table results in
1686 // multiple rows being returned for the same host.
1687 {3,
1689 "get_host_subid4_dhcpid",
1690 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1691 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1692 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1693 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1694 " h.dhcp4_boot_file_name, h.auth_key, "
1695 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1696 " o.persistent, o.user_context "
1697 "FROM hosts AS h "
1698 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1699 "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1700 " AND h.dhcp_identifier = $3 "
1701 "ORDER BY h.host_id, o.option_id"
1702 },
1703
1704 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
1705 // Retrieves host information, IPv6 reservations and DHCPv6 options
1706 // associated with a host. The number of rows returned is a multiplication
1707 // of number of IPv6 reservations and DHCPv6 options.
1708 {3,
1710 "get_host_subid6_dhcpid",
1711 "SELECT h.host_id, h.dhcp_identifier, "
1712 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1713 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1714 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1715 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1716 " h.dhcp4_boot_file_name, h.auth_key, "
1717 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1718 " o.persistent, o.user_context, "
1719 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1720 "FROM hosts AS h "
1721 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1722 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1723 "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1724 " AND h.dhcp_identifier = $3 "
1725 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1726 },
1727
1728 // PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
1729 // Retrieves host information and DHCPv4 options for the host using subnet
1730 // identifier and IPv4 reservation. Left joining the dhcp4_options table
1731 // results in multiple rows being returned for the host. The number of
1732 // rows depends on the number of options defined for the host.
1733 {2,
1734 { OID_INT8, OID_INT8 },
1735 "get_host_subid_addr",
1736 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1737 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1738 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1739 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1740 " h.dhcp4_boot_file_name, h.auth_key, "
1741 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1742 " o.persistent, o.user_context "
1743 "FROM hosts AS h "
1744 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1745 "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
1746 "ORDER BY h.host_id, o.option_id"
1747 },
1748
1749 // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
1750 // Retrieves host information, IPv6 reservations and DHCPv6 options
1751 // associated with a host using prefix and prefix length. This query
1752 // returns host information for a single host. However, multiple rows
1753 // are returned due to left joining IPv6 reservations and DHCPv6 options.
1754 // The number of rows returned is multiplication of number of existing
1755 // IPv6 reservations and DHCPv6 options.
1756 {2,
1757 { OID_VARCHAR, OID_INT2 },
1758 "get_host_prefix",
1759 "SELECT h.host_id, h.dhcp_identifier, "
1760 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1761 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1762 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1763 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1764 " h.dhcp4_boot_file_name, h.auth_key, "
1765 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1766 " o.persistent, o.user_context, "
1767 " r.reservation_id, r.address, r.prefix_len, r.type, "
1768 " r.dhcp6_iaid "
1769 "FROM hosts AS h "
1770 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1771 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1772 "WHERE h.host_id = "
1773 " (SELECT host_id FROM ipv6_reservations "
1774 " WHERE address = $1 AND prefix_len = $2) "
1775 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1776 },
1777
1778 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR
1779 // Retrieves host information, IPv6 reservations and DHCPv6 options
1780 // associated with a host using IPv6 subnet id and prefix. This query
1781 // returns host information for a single host. However, multiple rows
1782 // are returned due to left joining IPv6 reservations and DHCPv6 options.
1783 // The number of rows returned is multiplication of number of existing
1784 // IPv6 reservations and DHCPv6 options.
1785 {2,
1786 { OID_INT8, OID_VARCHAR },
1787 "get_host_subid6_addr",
1788 "SELECT h.host_id, h.dhcp_identifier, "
1789 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1790 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1791 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1792 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1793 " h.dhcp4_boot_file_name, h.auth_key, "
1794 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1795 " o.persistent, o.user_context, "
1796 " r.reservation_id, r.address, r.prefix_len, r.type, "
1797 " r.dhcp6_iaid "
1798 "FROM hosts AS h "
1799 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1800 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1801 "WHERE h.dhcp6_subnet_id = $1 AND r.address = $2 "
1802 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1803 },
1804
1805 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4
1806 //
1807 // Retrieves host information for all hosts in a subnet, along with the
1808 // DHCPv4 options associated with it. Left joining the dhcp4_options table
1809 // results in multiple rows being returned for the same host. The hosts are
1810 // retrieved by subnet id.
1811 {1,
1812 { OID_INT8 },
1813 "get_host_subid4",
1814 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1815 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1816 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1817 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1818 " h.dhcp4_boot_file_name, h.auth_key, "
1819 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1820 " o.persistent, o.user_context "
1821 "FROM hosts AS h "
1822 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1823 "WHERE h.dhcp4_subnet_id = $1 "
1824 "ORDER BY h.host_id, o.option_id"
1825 },
1826
1827 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6
1828 //
1829 // Retrieves host information, IPv6 reservations and DHCPv6 options
1830 // associated with all hosts using the IPv6 subnet id. This query returns
1831 // host information for many hosts. However, multiple rows are
1832 // returned due to left joining IPv6 reservations and DHCPv6 options.
1833 // The number of rows returned is multiplication of number of existing
1834 // IPv6 reservations and DHCPv6 options for each host in a subnet. There
1835 // are usually many hosts in a subnet. The amount of returned data may
1836 // be huge.
1837 {1,
1838 { OID_INT8 },
1839 "get_host_subid6",
1840 "SELECT h.host_id, h.dhcp_identifier, "
1841 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1842 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1843 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1844 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1845 " h.dhcp4_boot_file_name, h.auth_key, "
1846 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1847 " o.persistent, o.user_context, "
1848 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1849 "FROM hosts AS h "
1850 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1851 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1852 "WHERE h.dhcp6_subnet_id = $1 "
1853 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1854 },
1855
1856 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME
1857 // Retrieves host information, IPv6 reservations and both DHCPv4 and
1858 // DHCPv6 options associated with all hosts using the hostname.
1859 // The LEFT JOIN clause is used to retrieve information from 4 different
1860 // tables using a single query. Hence, this query returns multiple rows
1861 // for a single host.
1862 {1,
1863 { OID_VARCHAR },
1864 "get_host_hostname",
1865 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1866 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1867 " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1868 " h.user_context, "
1869 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1870 " h.dhcp4_boot_file_name, h.auth_key, "
1871 " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1872 " o4.persistent, o4.user_context, "
1873 " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1874 " o6.persistent, o6.user_context, "
1875 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1876 "FROM hosts AS h "
1877 "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1878 "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1879 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1880 "WHERE lower(h.hostname) = $1 "
1881 "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1882 },
1883
1884 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4
1885 // Retrieves host information for all hosts with a hostname in a subnet,
1886 // along with the DHCPv4 options associated with it. Left joining
1887 // the dhcp4_options table results in multiple rows being returned for
1888 // the same host.
1889 {2,
1890 { OID_VARCHAR, OID_INT8 },
1891 "get_host_hostname_subid4",
1892 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1893 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1894 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1895 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1896 " h.dhcp4_boot_file_name, h.auth_key, "
1897 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1898 " o.persistent, o.user_context "
1899 "FROM hosts AS h "
1900 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1901 "WHERE lower(h.hostname) = $1 AND h.dhcp4_subnet_id = $2 "
1902 "ORDER BY h.host_id, o.option_id"
1903 },
1904
1905 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6
1906 // Retrieves host information, IPv6 reservations and DHCPv6 options
1907 // associated with all hosts using the hostname and the IPv6 subnet id.
1908 // This query returns host information for many hosts. However, multiple
1909 // rows are returned due to left joining IPv6 reservations and DHCPv6
1910 // options. The number of rows returned is multiplication of number of
1911 // existing IPv6 reservations and DHCPv6 options for each host in a subnet.
1912 {2,
1913 { OID_VARCHAR, OID_INT8 },
1914 "get_host_hostname_subid6",
1915 "SELECT h.host_id, h.dhcp_identifier, "
1916 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1917 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1918 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1919 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1920 " h.dhcp4_boot_file_name, h.auth_key, "
1921 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1922 " o.persistent, o.user_context, "
1923 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1924 "FROM hosts AS h "
1925 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1926 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1927 "WHERE lower(h.hostname) = $1 AND h.dhcp6_subnet_id = $2 "
1928 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1929 },
1930
1931 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE
1932 // Retrieves host information along with the DHCPv4 options associated with
1933 // it. Left joining the dhcp4_options table results in multiple rows being
1934 // returned for the same host. The hosts are retrieved by subnet id,
1935 // starting from specified host id. Specified number of hosts is returned.
1936 {3,
1938 "get_host_subid4_page",
1939 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1940 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1941 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1942 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1943 " h.dhcp4_boot_file_name, h.auth_key, "
1944 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1945 " o.persistent, o.user_context "
1946 "FROM ( SELECT * FROM hosts AS h "
1947 " WHERE h.dhcp4_subnet_id = $1 AND h.host_id > $2 "
1948 " ORDER BY h.host_id "
1949 " LIMIT $3 ) AS h "
1950 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1951 "ORDER BY h.host_id, o.option_id"
1952 },
1953
1954 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE
1955 // Retrieves host information, IPv6 reservations and DHCPv6 options
1956 // associated with a host using IPv6 subnet id. This query returns
1957 // host information for a single host. However, multiple rows are
1958 // returned due to left joining IPv6 reservations and DHCPv6 options.
1959 // The number of rows returned is multiplication of number of existing
1960 // IPv6 reservations and DHCPv6 options.
1961 {3,
1963 "get_host_subid6_page",
1964 "SELECT h.host_id, h.dhcp_identifier, "
1965 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1966 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1967 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1968 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1969 " h.dhcp4_boot_file_name, h.auth_key, "
1970 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1971 " o.persistent, o.user_context, "
1972 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1973 "FROM ( SELECT * FROM hosts AS h "
1974 " WHERE h.dhcp6_subnet_id = $1 AND h.host_id > $2 "
1975 " ORDER BY h.host_id "
1976 " LIMIT $3 ) AS h "
1977 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1978 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1979 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1980 },
1981
1982 // PgSqlHostDataSourceImpl::GET_HOST_PAGE4
1983 // Retrieves host information along with the DHCPv4 options associated with
1984 // it. Left joining the dhcp4_options table results in multiple rows being
1985 // returned for the same host. The hosts are retrieved starting from
1986 // specified host id. Specified number of hosts is returned.
1987 {2,
1988 { OID_INT8, OID_INT8 },
1989 "get_host_page4",
1990 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1991 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1992 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1993 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1994 " h.dhcp4_boot_file_name, h.auth_key, "
1995 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1996 " o.persistent, o.user_context "
1997 "FROM ( SELECT * FROM hosts AS h "
1998 " WHERE h.host_id > $1 "
1999 " ORDER BY h.host_id "
2000 " LIMIT $2 ) AS h "
2001 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
2002 "ORDER BY h.host_id, o.option_id"
2003 },
2004
2005 // PgSqlHostDataSourceImpl::GET_HOST_PAGE6
2006 // Retrieves host information, IPv6 reservations and DHCPv6 options
2007 // associated with a host using IPv6 subnet id. This query returns
2008 // host information for a single host. However, multiple rows are
2009 // returned due to left joining IPv6 reservations and DHCPv6 options.
2010 // The number of rows returned is multiplication of number of existing
2011 // IPv6 reservations and DHCPv6 options.
2012 {2,
2013 { OID_INT8, OID_INT8 },
2014 "get_host_page6",
2015 "SELECT h.host_id, h.dhcp_identifier, "
2016 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2017 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2018 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2019 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
2020 " h.dhcp4_boot_file_name, h.auth_key, "
2021 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
2022 " o.persistent, o.user_context, "
2023 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
2024 "FROM ( SELECT * FROM hosts AS h "
2025 " WHERE h.host_id > $1 "
2026 " ORDER BY h.host_id "
2027 " LIMIT $2 ) AS h "
2028 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
2029 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
2030 "ORDER BY h.host_id, o.option_id, r.reservation_id"
2031 },
2032
2033 // PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP
2034 // Inserts a host into the 'hosts' table without checking that there is
2035 // a reservation for the IP address.
2036 {13,
2041 "insert_host_non_unique_ip",
2042 "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2043 " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2044 " dhcp4_client_classes, dhcp6_client_classes, user_context, "
2045 " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2046 "VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) "
2047 "RETURNING host_id"
2048 },
2049
2050 // PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP
2051 // Inserts a host into the 'hosts' table with checking that reserved IP
2052 // address is unique. The innermost query checks if there is at least
2053 // one host for the given IP/subnet combination. For checking whether
2054 // hosts exists or not it doesn't matter if we select actual columns,
2055 // thus SELECT 1 was used as an optimization to avoid selecting data
2056 // that will be ignored anyway. If it does not exist the new host is
2057 // inserted. If the host with the given IP address already exists the
2058 // new host won't be inserted. The caller can check the number of
2059 // affected rows to detect that there was a duplicate host in the
2060 // database. Returns the inserted host id.
2061 {15,
2067 "insert_host_unique_ip",
2068 "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2069 " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2070 " dhcp4_client_classes, dhcp6_client_classes, user_context, "
2071 " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2072 " SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
2073 " WHERE NOT EXISTS ("
2074 " SELECT 1 FROM hosts WHERE ipv4_address = $14 AND dhcp4_subnet_id = $15"
2075 " LIMIT 1"
2076 " ) "
2077 "RETURNING host_id"
2078 },
2079
2080 // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE
2081 // Inserts a single IPv6 reservation into 'reservations' table without
2082 // checking that the inserted reservation is unique.
2083 {5,
2085 "insert_v6_resrv_non_unique",
2086 "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2087 " dhcp6_iaid, host_id) "
2088 "VALUES ($1, $2, $3, $4, $5)"
2089 },
2090
2091 // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE
2092 // Inserts a single IPv6 reservation into 'reservations' table with
2093 // checking that the inserted reservation is unique.
2094 {7,
2096 "insert_v6_resrv_unique",
2097 "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2098 " dhcp6_iaid, host_id) "
2099 "SELECT $1, $2, $3, $4, $5 "
2100 " WHERE NOT EXISTS ("
2101 " SELECT 1 FROM ipv6_reservations"
2102 " WHERE address = $6 AND prefix_len = $7"
2103 " LIMIT 1"
2104 " )"
2105 },
2106
2107 // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
2108 // Inserts a single DHCPv4 option into 'dhcp4_options' table.
2109 // Using fixed scope_id = 3, which associates an option with host.
2110 {7,
2113 "insert_v4_host_option",
2114 "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
2115 " persistent, user_context, host_id, scope_id) "
2116 "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2117 },
2118
2119 // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
2120 // Inserts a single DHCPv6 option into 'dhcp6_options' table.
2121 // Using fixed scope_id = 3, which associates an option with host.
2122 {7,
2125 "insert_v6_host_option",
2126 "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
2127 " persistent, user_context, host_id, scope_id) "
2128 "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2129 },
2130
2131 // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
2132 // Deletes a v4 host that matches (subnet-id, addr4)
2133 {2,
2134 { OID_INT8, OID_INT8 },
2135 "del_host_addr4",
2136 "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
2137 },
2138
2139 // PgSqlHostDataSourceImpl::DEL_HOST_ADDR6
2140 // Deletes a v6 host that matches (subnet-id, addr6)
2141 {2,
2142 { OID_INT8, OID_VARCHAR },
2143 "del_host_addr6",
2144 "DELETE FROM hosts USING ipv6_reservations "
2145 " WHERE dhcp6_subnet_id = $1 AND ipv6_reservations.address = $2"
2146 },
2147
2148 // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
2149 // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
2150 {3,
2152 "del_host_subid4_id",
2153 "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
2154 "AND dhcp_identifier_type = $2 "
2155 "AND dhcp_identifier = $3"
2156 },
2157
2158 // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
2159 // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
2160 {3,
2162 "del_host_subid6_id",
2163 "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
2164 "AND dhcp_identifier_type = $2 "
2165 "AND dhcp_identifier = $3"
2166 }
2167}
2168};
2169
2170} // namespace
2171
2172// PgSqlHostContext Constructor
2173
2175 IOServiceAccessorPtr io_service_accessor,
2176 db::DbCallback db_reconnect_callback)
2177 : conn_(parameters, io_service_accessor, db_reconnect_callback),
2178 is_readonly_(true) {
2179}
2180
2181// PgSqlHostContextAlloc Constructor and Destructor
2182
2184 PgSqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
2185
2186 if (MultiThreadingMgr::instance().getMode()) {
2187 // multi-threaded
2188 {
2189 // we need to protect the whole pool_ operation, hence extra scope {}
2190 lock_guard<mutex> lock(mgr_.pool_->mutex_);
2191 if (!mgr_.pool_->pool_.empty()) {
2192 ctx_ = mgr_.pool_->pool_.back();
2193 mgr_.pool_->pool_.pop_back();
2194 }
2195 }
2196 if (!ctx_) {
2197 ctx_ = mgr_.createContext();
2198 }
2199 } else {
2200 // single-threaded
2201 if (mgr_.pool_->pool_.empty()) {
2202 isc_throw(Unexpected, "No available PostgreSQL host context?!");
2203 }
2204 ctx_ = mgr_.pool_->pool_.back();
2205 }
2206}
2207
2209 if (MultiThreadingMgr::instance().getMode()) {
2210 // multi-threaded
2211 lock_guard<mutex> lock(mgr_.pool_->mutex_);
2212 mgr_.pool_->pool_.push_back(ctx_);
2213 if (ctx_->conn_.isUnusable()) {
2214 mgr_.unusable_ = true;
2215 }
2216 } else if (ctx_->conn_.isUnusable()) {
2217 mgr_.unusable_ = true;
2218 }
2219}
2220
2222 : parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
2223 timer_name_("") {
2224
2225 // Create unique timer name per instance.
2226 timer_name_ = "PgSqlHostMgr[";
2227 timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
2228 timer_name_ += "]DbReconnectTimer";
2229
2230 // Validate the schema version first.
2231 std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
2233 std::pair<uint32_t, uint32_t> db_version = getVersion();
2234 if (code_version != db_version) {
2236 "PostgreSQL schema version mismatch: need version: "
2237 << code_version.first << "." << code_version.second
2238 << " found version: " << db_version.first << "."
2239 << db_version.second);
2240 }
2241
2242 // Create an initial context.
2243 pool_.reset(new PgSqlHostContextPool());
2244 pool_->pool_.push_back(createContext());
2245}
2246
2247// Create context.
2248
2254
2255 // Open the database.
2256 ctx->conn_.openDatabase();
2257
2258 // Now prepare the SQL statements.
2259 ctx->conn_.prepareStatements(tagged_statements.begin(),
2260 tagged_statements.begin() + WRITE_STMTS_BEGIN);
2261
2262 // Check if the backend is explicitly configured to operate with
2263 // read only access to the database.
2264 ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
2265
2266 // If we are using read-write mode for the database we also prepare
2267 // statements for INSERTS etc.
2268 if (!ctx->is_readonly_) {
2269 ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
2270 tagged_statements.end());
2271 } else {
2273 }
2274
2275 ctx->host_ipv4_exchange_.reset(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY));
2276 ctx->host_ipv6_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY));
2277 ctx->host_ipv46_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
2278 ctx->host_ipv6_reservation_exchange_.reset(new PgSqlIPv6ReservationExchange());
2279 ctx->host_option_exchange_.reset(new PgSqlOptionExchange());
2280
2281 // Create ReconnectCtl for this connection.
2282 ctx->conn_.makeReconnectCtl(timer_name_);
2283
2284 return (ctx);
2285}
2286
2288}
2289
2290bool
2293
2294 // Invoke application layer connection lost callback.
2295 if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
2296 return (false);
2297 }
2298
2299 bool reopened = false;
2300
2301 const std::string timer_name = db_reconnect_ctl->timerName();
2302
2303 // At least one connection was lost.
2304 try {
2305 CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
2306 std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
2307 for (std::string& hds : host_db_access_list) {
2308 auto parameters = DatabaseConnection::parse(hds);
2309 if (HostMgr::delBackend("postgresql", hds, true)) {
2311 }
2312 }
2313 reopened = true;
2314 } catch (const std::exception& ex) {
2316 .arg(ex.what());
2317 }
2318
2319 if (reopened) {
2320 // Cancel the timer.
2321 if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2322 TimerMgr::instance()->unregisterTimer(timer_name);
2323 }
2324
2325 // Invoke application layer connection recovered callback.
2326 if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
2327 return (false);
2328 }
2329 } else {
2330 if (!db_reconnect_ctl->checkRetries()) {
2331 // We're out of retries, log it and initiate shutdown.
2333 .arg(db_reconnect_ctl->maxRetries());
2334
2335 // Cancel the timer.
2336 if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2337 TimerMgr::instance()->unregisterTimer(timer_name);
2338 }
2339
2340 // Invoke application layer connection failed callback.
2342 return (false);
2343 }
2344
2346 .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
2347 .arg(db_reconnect_ctl->maxRetries())
2348 .arg(db_reconnect_ctl->retryInterval());
2349
2350 // Start the timer.
2351 if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
2352 TimerMgr::instance()->registerTimer(timer_name,
2353 std::bind(&PgSqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
2354 db_reconnect_ctl->retryInterval(),
2356 }
2357 TimerMgr::instance()->setup(timer_name);
2358 }
2359
2360 return (true);
2361}
2362
2363uint64_t
2365 StatementIndex stindex,
2366 PsqlBindArrayPtr& bind_array,
2367 const bool return_last_id) {
2368 uint64_t last_id = 0;
2369 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2370 tagged_statements[stindex].nbparams,
2371 &bind_array->values_[0],
2372 &bind_array->lengths_[0],
2373 &bind_array->formats_[0], 0));
2374
2375 int s = PQresultStatus(r);
2376
2377 if (s != PGRES_COMMAND_OK) {
2378 // Failure: check for the special case of duplicate entry.
2379 if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
2380 isc_throw(DuplicateEntry, "Database duplicate entry error");
2381 }
2382
2383 // Connection determines if the error is fatal or not, and
2384 // throws the appropriate exception
2385 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2386 }
2387
2388 // Get the number of affected rows.
2389 char* rows_affected = PQcmdTuples(r);
2390 if (!rows_affected) {
2392 "Could not retrieve the number of affected rows.");
2393 }
2394
2395 // If the number of rows inserted is 0 it means that the query detected
2396 // an attempt to insert duplicated data for which there is no unique
2397 // index in the database. Unique indexes are not created in the database
2398 // when it may be sometimes allowed to insert duplicated records per
2399 // server's configuration.
2400 if (rows_affected[0] == '0') {
2401 isc_throw(DuplicateEntry, "Database duplicate entry error");
2402 }
2403
2404 if (return_last_id) {
2405 PgSqlExchange::getColumnValue(r, 0, 0, last_id);
2406 }
2407
2408 return (last_id);
2409}
2410
2411bool
2413 StatementIndex stindex,
2414 PsqlBindArrayPtr& bind_array) {
2415 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2416 tagged_statements[stindex].nbparams,
2417 &bind_array->values_[0],
2418 &bind_array->lengths_[0],
2419 &bind_array->formats_[0], 0));
2420
2421 int s = PQresultStatus(r);
2422
2423 if (s != PGRES_COMMAND_OK) {
2424 // Connection determines if the error is fatal or not, and
2425 // throws the appropriate exception
2426 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2427 }
2428
2429 // Now check how many rows (hosts) were deleted. This should be either
2430 // "0" or "1".
2431 char* rows_deleted = PQcmdTuples(r);
2432 if (!rows_deleted) {
2434 "Could not retrieve the number of deleted rows.");
2435 }
2436 return (rows_deleted[0] != '0');
2437}
2438
2439void
2441 const IPv6Resrv& resv,
2442 const HostID& id) {
2443 PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->
2444 createBindForSend(resv, id, ip_reservations_unique_);
2445
2446 addStatement(ctx,
2448 bind_array);
2449}
2450
2451void
2453 const StatementIndex& stindex,
2454 const OptionDescriptor& opt_desc,
2455 const std::string& opt_space,
2456 const Optional<SubnetID>&,
2457 const HostID& id) {
2458 PsqlBindArrayPtr bind_array = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, id);
2459
2460 addStatement(ctx, stindex, bind_array);
2461}
2462
2463void
2465 const StatementIndex& stindex,
2466 const ConstCfgOptionPtr& options_cfg,
2467 const uint64_t host_id) {
2468 // Get option space names and vendor space names and combine them within a
2469 // single list.
2470 std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
2471 std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
2472 option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
2473 vendor_spaces.end());
2474
2475 // For each option space retrieve all options and insert them into the
2476 // database.
2477 for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
2478 OptionContainerPtr options = options_cfg->getAll(*space);
2479 if (options && !options->empty()) {
2480 for (auto opt = options->begin(); opt != options->end(); ++opt) {
2481 addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
2482 }
2483 }
2484 }
2485}
2486
2487void
2489 StatementIndex stindex,
2490 PsqlBindArrayPtr bind_array,
2491 boost::shared_ptr<PgSqlHostExchange> exchange,
2492 ConstHostCollection& result,
2493 bool single) const {
2494
2495 exchange->clear();
2496 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2497 tagged_statements[stindex].nbparams,
2498 &bind_array->values_[0],
2499 &bind_array->lengths_[0],
2500 &bind_array->formats_[0], 0));
2501
2502 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2503
2504 int rows = r.getRows();
2505 for (int row = 0; row < rows; ++row) {
2506 exchange->processRowData(result, r, row);
2507
2508 if (single && result.size() > 1) {
2509 isc_throw(MultipleRecords, "multiple records were found in the "
2510 "database where only one was expected for query "
2511 << tagged_statements[stindex].name);
2512 }
2513 }
2514}
2515
2518 const SubnetID& subnet_id,
2519 const Host::IdentifierType& identifier_type,
2520 const uint8_t* identifier_begin,
2521 const size_t identifier_len,
2522 StatementIndex stindex,
2523 boost::shared_ptr<PgSqlHostExchange> exchange) const {
2524
2525 // Set up the WHERE clause value
2526 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2527
2528 // Add the subnet id.
2529 bind_array->add(subnet_id);
2530
2531 // Add the Identifier type.
2532 bind_array->add(static_cast<uint8_t>(identifier_type));
2533
2534 // Add the identifier value.
2535 bind_array->add(identifier_begin, identifier_len);
2536
2537 ConstHostCollection collection;
2538 getHostCollection(ctx, stindex, bind_array, exchange, collection, true);
2539
2540 // Return single record if present, else clear the host.
2541 ConstHostPtr result;
2542 if (!collection.empty()) {
2543 result = *collection.begin();
2544 }
2545
2546 return (result);
2547}
2548
2549std::pair<uint32_t, uint32_t>
2554}
2555
2556void
2558 if (ctx->is_readonly_) {
2559 isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
2560 " to operate in read only mode");
2561 }
2562}
2563
2564/*********** PgSqlHostDataSource *********************/
2565
2567 : impl_(new PgSqlHostDataSourceImpl(parameters)) {
2568}
2569
2571}
2572
2575 return (impl_->parameters_);
2576}
2577
2578void
2580 // Get a context
2581 PgSqlHostContextAlloc get_context(*impl_);
2582 PgSqlHostContextPtr ctx = get_context.ctx_;
2583
2584 // If operating in read-only mode, throw exception.
2585 impl_->checkReadOnly(ctx);
2586
2587 // Initiate PostgreSQL transaction as we will have to make multiple queries
2588 // to insert host information into multiple tables. If that fails on
2589 // any stage, the transaction will be rolled back by the destructor of
2590 // the PgSqlTransaction class.
2591 PgSqlTransaction transaction(ctx->conn_);
2592
2593 // If we're configured to check that an IP reservation within a given subnet
2594 // is unique, the IP reservation exists and the subnet is actually set
2595 // we will be using a special query that checks for uniqueness. Otherwise,
2596 // we will use a regular insert statement.
2597 bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
2598 && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
2599
2600 // Create the PgSQL Bind array for the host
2601 PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
2602
2603 // ... and insert the host.
2604 uint32_t host_id = impl_->addStatement(ctx,
2607 bind_array, true);
2608
2609 // Insert DHCPv4 options.
2610 ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
2611 if (cfg_option4) {
2613 cfg_option4, host_id);
2614 }
2615
2616 // Insert DHCPv6 options.
2617 ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
2618 if (cfg_option6) {
2620 cfg_option6, host_id);
2621 }
2622
2623 // Insert IPv6 reservations.
2624 IPv6ResrvRange v6resv = host->getIPv6Reservations();
2625 if (std::distance(v6resv.first, v6resv.second) > 0) {
2626 for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
2627 ++resv) {
2628 impl_->addResv(ctx, resv->second, host_id);
2629 }
2630 }
2631
2632 // Everything went fine, so explicitly commit the transaction.
2633 transaction.commit();
2634}
2635
2636bool
2638 const asiolink::IOAddress& addr) {
2639 // Get a context
2640 PgSqlHostContextAlloc get_context(*impl_);
2641 PgSqlHostContextPtr ctx = get_context.ctx_;
2642
2643 // If operating in read-only mode, throw exception.
2644 impl_->checkReadOnly(ctx);
2645
2646 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2647 bind_array->add(subnet_id);
2648
2649 // v4
2650 if (addr.isV4()) {
2651 bind_array->add(addr);
2652 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
2653 bind_array));
2654 }
2655
2656 // v6
2657 bind_array->add(addr.toText());
2658
2659 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR6,
2660 bind_array));
2661}
2662
2663bool
2665 const Host::IdentifierType& identifier_type,
2666 const uint8_t* identifier_begin,
2667 const size_t identifier_len) {
2668 // Get a context
2669 PgSqlHostContextAlloc get_context(*impl_);
2670 PgSqlHostContextPtr ctx = get_context.ctx_;
2671
2672 // If operating in read-only mode, throw exception.
2673 impl_->checkReadOnly(ctx);
2674
2675 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2676
2677 // Subnet-id
2678 bind_array->add(subnet_id);
2679
2680 // identifier-type
2681 bind_array->add(static_cast<uint8_t>(identifier_type));
2682
2683 // identifier
2684 bind_array->add(identifier_begin, identifier_len);
2685
2686 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
2687 bind_array));
2688}
2689
2690bool
2692 const Host::IdentifierType& identifier_type,
2693 const uint8_t* identifier_begin,
2694 const size_t identifier_len) {
2695 // Get a context
2696 PgSqlHostContextAlloc get_context(*impl_);
2697 PgSqlHostContextPtr ctx = get_context.ctx_;
2698
2699 // If operating in read-only mode, throw exception.
2700 impl_->checkReadOnly(ctx);
2701
2702 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2703
2704 // Subnet-id
2705 bind_array->add(subnet_id);
2706
2707 // identifier-type
2708 bind_array->add(static_cast<uint8_t>(identifier_type));
2709
2710 // identifier
2711 bind_array->add(identifier_begin, identifier_len);
2712
2713 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
2714 bind_array));
2715}
2716
2719 const uint8_t* identifier_begin,
2720 const size_t identifier_len) const {
2721 // Get a context
2722 PgSqlHostContextAlloc get_context(*impl_);
2723 PgSqlHostContextPtr ctx = get_context.ctx_;
2724
2725 // Set up the WHERE clause value
2726 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2727
2728 // Identifier value.
2729 bind_array->add(identifier_begin, identifier_len);
2730
2731 // Identifier type.
2732 bind_array->add(static_cast<uint8_t>(identifier_type));
2733
2734 ConstHostCollection result;
2735 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
2736 bind_array, ctx->host_ipv46_exchange_, result, false);
2737
2738 return (result);
2739}
2740
2743 // Get a context
2744 PgSqlHostContextAlloc get_context(*impl_);
2745 PgSqlHostContextPtr ctx = get_context.ctx_;
2746
2747 // Set up the WHERE clause value
2748 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2749
2750 // Add the subnet id.
2751 bind_array->add(subnet_id);
2752
2753 ConstHostCollection result;
2754 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4,
2755 bind_array, ctx->host_ipv4_exchange_, result, false);
2756
2757 return (result);
2758}
2759
2762 // Get a context
2763 PgSqlHostContextAlloc get_context(*impl_);
2764 PgSqlHostContextPtr ctx = get_context.ctx_;
2765
2766 // Set up the WHERE clause value
2767 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2768
2769 // Add the subnet id.
2770 bind_array->add(subnet_id);
2771
2772 ConstHostCollection result;
2773 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6,
2774 bind_array, ctx->host_ipv6_exchange_, result, false);
2775
2776 return (result);
2777}
2778
2780PgSqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
2781 // Get a context
2782 PgSqlHostContextAlloc get_context(*impl_);
2783 PgSqlHostContextPtr ctx = get_context.ctx_;
2784
2785 // Set up the WHERE clause value
2786 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2787
2788 // Add the hostname.
2789 bind_array->add(hostname);
2790
2791 ConstHostCollection result;
2792 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME,
2793 bind_array, ctx->host_ipv46_exchange_, result, false);
2794
2795 return (result);
2796}
2797
2799PgSqlHostDataSource::getAllbyHostname4(const std::string& hostname,
2800 const SubnetID& subnet_id) const {
2801 // Get a context
2802 PgSqlHostContextAlloc get_context(*impl_);
2803 PgSqlHostContextPtr ctx = get_context.ctx_;
2804
2805 // Set up the WHERE clause value
2806 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2807
2808 // Add the hostname.
2809 bind_array->add(hostname);
2810
2811 // Add the subnet id.
2812 bind_array->add(subnet_id);
2813
2814 ConstHostCollection result;
2815 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
2816 bind_array, ctx->host_ipv4_exchange_, result, false);
2817
2818 return (result);
2819}
2820
2822PgSqlHostDataSource::getAllbyHostname6(const std::string& hostname,
2823 const SubnetID& subnet_id) const {
2824 // Get a context
2825 PgSqlHostContextAlloc get_context(*impl_);
2826 PgSqlHostContextPtr ctx = get_context.ctx_;
2827
2828 // Set up the WHERE clause value
2829 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2830
2831 // Add the hostname.
2832 bind_array->add(hostname);
2833
2834 // Add the subnet id.
2835 bind_array->add(subnet_id);
2836
2837 ConstHostCollection result;
2838 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
2839 bind_array, ctx->host_ipv6_exchange_, result, false);
2840
2841 return (result);
2842}
2843
2846 size_t& /*source_index*/,
2847 uint64_t lower_host_id,
2848 const HostPageSize& page_size) const {
2849 // Get a context
2850 PgSqlHostContextAlloc get_context(*impl_);
2851 PgSqlHostContextPtr ctx = get_context.ctx_;
2852
2853 // Set up the WHERE clause value
2854 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2855
2856 // Add the subnet id.
2857 bind_array->add(subnet_id);
2858
2859 // Add the lower bound host id.
2860 bind_array->add(lower_host_id);
2861
2862 // Add the page size value.
2863 string page_size_data =
2864 boost::lexical_cast<std::string>(page_size.page_size_);
2865 bind_array->add(page_size_data);
2866
2867 ConstHostCollection result;
2868 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
2869 bind_array, ctx->host_ipv4_exchange_, result, false);
2870
2871 return (result);
2872}
2873
2876 size_t& /*source_index*/,
2877 uint64_t lower_host_id,
2878 const HostPageSize& page_size) const {
2879 // Get a context
2880 PgSqlHostContextAlloc get_context(*impl_);
2881 PgSqlHostContextPtr ctx = get_context.ctx_;
2882
2883 // Set up the WHERE clause value
2884 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2885
2886 // Add the subnet id.
2887 bind_array->add(subnet_id);
2888
2889 // Add the lower bound host id.
2890 bind_array->add(lower_host_id);
2891
2892 // Add the page size value.
2893 string page_size_data =
2894 boost::lexical_cast<std::string>(page_size.page_size_);
2895 bind_array->add(page_size_data);
2896
2897 ConstHostCollection result;
2898 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
2899 bind_array, ctx->host_ipv6_exchange_, result, false);
2900
2901 return (result);
2902}
2903
2905PgSqlHostDataSource::getPage4(size_t& /*source_index*/,
2906 uint64_t lower_host_id,
2907 const HostPageSize& page_size) const {
2908 // Get a context
2909 PgSqlHostContextAlloc get_context(*impl_);
2910 PgSqlHostContextPtr ctx = get_context.ctx_;
2911
2912 // Set up the WHERE clause value
2913 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2914
2915 // Add the lower bound host id.
2916 bind_array->add(lower_host_id);
2917
2918 // Add the page size value.
2919 string page_size_data =
2920 boost::lexical_cast<std::string>(page_size.page_size_);
2921 bind_array->add(page_size_data);
2922
2923 ConstHostCollection result;
2924 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE4,
2925 bind_array, ctx->host_ipv4_exchange_, result, false);
2926
2927 return (result);
2928}
2929
2931PgSqlHostDataSource::getPage6(size_t& /*source_index*/,
2932 uint64_t lower_host_id,
2933 const HostPageSize& page_size) const {
2934 // Get a context
2935 PgSqlHostContextAlloc get_context(*impl_);
2936 PgSqlHostContextPtr ctx = get_context.ctx_;
2937
2938 // Set up the WHERE clause value
2939 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2940
2941 // Add the lower bound host id.
2942 bind_array->add(lower_host_id);
2943
2944 // Add the page size value.
2945 string page_size_data =
2946 boost::lexical_cast<std::string>(page_size.page_size_);
2947 bind_array->add(page_size_data);
2948
2949 ConstHostCollection result;
2950 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE6,
2951 bind_array, ctx->host_ipv6_exchange_, result, false);
2952
2953 return (result);
2954}
2955
2958 // Get a context
2959 PgSqlHostContextAlloc get_context(*impl_);
2960 PgSqlHostContextPtr ctx = get_context.ctx_;
2961
2962 // Set up the WHERE clause value
2963 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2964
2965 // v4 Reservation address
2966 bind_array->add(address);
2967
2968 ConstHostCollection result;
2969 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_ADDR,
2970 bind_array, ctx->host_ipv4_exchange_, result, false);
2971
2972 return (result);
2973}
2974
2977 const Host::IdentifierType& identifier_type,
2978 const uint8_t* identifier_begin,
2979 const size_t identifier_len) const {
2980 // Get a context
2981 PgSqlHostContextAlloc get_context(*impl_);
2982 PgSqlHostContextPtr ctx = get_context.ctx_;
2983
2984 return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
2986 ctx->host_ipv4_exchange_));
2987}
2988
2991 const asiolink::IOAddress& address) const {
2992 // Get a context
2993 PgSqlHostContextAlloc get_context(*impl_);
2994 PgSqlHostContextPtr ctx = get_context.ctx_;
2995
2996 if (!address.isV4()) {
2997 isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
2998 " wrong address type, address supplied is an IPv6 address");
2999 }
3000
3001 // Set up the WHERE clause value
3002 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3003
3004 // Add the subnet id
3005 bind_array->add(subnet_id);
3006
3007 // Add the address
3008 bind_array->add(address);
3009
3010 ConstHostCollection collection;
3011 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3012 bind_array, ctx->host_ipv4_exchange_, collection, true);
3013
3014 // Return single record if present, else clear the host.
3015 ConstHostPtr result;
3016 if (!collection.empty()) {
3017 result = *collection.begin();
3018 }
3019
3020 return (result);
3021}
3022
3025 const asiolink::IOAddress& address) const {
3026 // Get a context
3027 PgSqlHostContextAlloc get_context(*impl_);
3028 PgSqlHostContextPtr ctx = get_context.ctx_;
3029
3030 if (!address.isV4()) {
3031 isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
3032 " wrong address type, address supplied is an IPv6 address");
3033 }
3034
3035 // Set up the WHERE clause value
3036 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3037
3038 // Add the subnet id
3039 bind_array->add(subnet_id);
3040
3041 // Add the address
3042 bind_array->add(address);
3043
3044 ConstHostCollection collection;
3045 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3046 bind_array, ctx->host_ipv4_exchange_, collection, false);
3047 return (collection);
3048}
3049
3052 const Host::IdentifierType& identifier_type,
3053 const uint8_t* identifier_begin,
3054 const size_t identifier_len) const {
3055 // Get a context
3056 PgSqlHostContextAlloc get_context(*impl_);
3057 PgSqlHostContextPtr ctx = get_context.ctx_;
3058
3059 return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
3061 ctx->host_ipv6_exchange_));
3062}
3063
3066 const uint8_t prefix_len) const {
3067 if (!prefix.isV6()) {
3068 isc_throw(BadValue, "PgSqlHostDataSource::get6(prefix, prefix_len): "
3069 "wrong address type, address supplied is an IPv4 address");
3070 }
3071
3072 // Get a context
3073 PgSqlHostContextAlloc get_context(*impl_);
3074 PgSqlHostContextPtr ctx = get_context.ctx_;
3075
3076 // Set up the WHERE clause value
3077 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3078
3079 // Add the prefix
3080 bind_array->add(prefix);
3081
3082 // Add the prefix length
3083 bind_array->add(prefix_len);
3084
3085 ConstHostCollection collection;
3086 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
3087 bind_array, ctx->host_ipv6_exchange_, collection, true);
3088
3089 // Return single record if present, else clear the host.
3090 ConstHostPtr result;
3091 if (!collection.empty()) {
3092 result = *collection.begin();
3093 }
3094
3095 return (result);
3096}
3097
3100 const asiolink::IOAddress& address) const {
3101 if (!address.isV6()) {
3102 isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3103 "wrong address type, address supplied is an IPv4 address");
3104 }
3105
3106 // Get a context
3107 PgSqlHostContextAlloc get_context(*impl_);
3108 PgSqlHostContextPtr ctx = get_context.ctx_;
3109
3110 // Set up the WHERE clause value
3111 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3112
3113 // Add the subnet id
3114 bind_array->add(subnet_id);
3115
3116 // Add the prefix
3117 bind_array->add(address);
3118
3119 ConstHostCollection collection;
3120 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3121 bind_array, ctx->host_ipv6_exchange_, collection, true);
3122
3123 // Return single record if present, else clear the host.
3124 ConstHostPtr result;
3125 if (!collection.empty()) {
3126 result = *collection.begin();
3127 }
3128
3129 return (result);
3130}
3131
3134 const asiolink::IOAddress& address) const {
3135 if (!address.isV6()) {
3136 isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3137 "wrong address type, address supplied is an IPv4 address");
3138 }
3139
3140 // Get a context
3141 PgSqlHostContextAlloc get_context(*impl_);
3142 PgSqlHostContextPtr ctx = get_context.ctx_;
3143
3144 // Set up the WHERE clause value
3145 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3146
3147 // Add the subnet id
3148 bind_array->add(subnet_id);
3149
3150 // Add the prefix
3151 bind_array->add(address);
3152
3153 ConstHostCollection collection;
3154 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3155 bind_array, ctx->host_ipv6_exchange_, collection, false);
3156 return (collection);
3157}
3158
3159
3160// Miscellaneous database methods.
3161
3162std::string
3164 std::string name = "";
3165 // Get a context
3166 PgSqlHostContextAlloc get_context(*impl_);
3167 PgSqlHostContextPtr ctx = get_context.ctx_;
3168
3169 try {
3170 name = ctx->conn_.getParameter("name");
3171 } catch (...) {
3172 // Return an empty name
3173 }
3174 return (name);
3175}
3176
3177std::string
3179 return (std::string("Host data source that stores host information"
3180 "in PostgreSQL database"));
3181}
3182
3183std::pair<uint32_t, uint32_t>
3185 return(impl_->getVersion());
3186}
3187
3188void
3190 // Get a context
3191 PgSqlHostContextAlloc get_context(*impl_);
3192 PgSqlHostContextPtr ctx = get_context.ctx_;
3193
3194 // If operating in read-only mode, throw exception.
3195 impl_->checkReadOnly(ctx);
3196 ctx->conn_.commit();
3197}
3198
3199void
3201 // Get a context
3202 PgSqlHostContextAlloc get_context(*impl_);
3203 PgSqlHostContextPtr ctx = get_context.ctx_;
3204
3205 // If operating in read-only mode, throw exception.
3206 impl_->checkReadOnly(ctx);
3207 ctx->conn_.rollback();
3208}
3209
3210bool
3212 impl_->ip_reservations_unique_ = unique;
3213 return (true);
3214}
3215
3216bool
3218 return (impl_->unusable_);
3219}
3220
3221} // namespace dhcp
3222} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when an unexpected error condition occurs.
A standard Data module exception that is thrown if a parse error is encountered when constructing an ...
Definition: data.h:47
static bool invokeDbLostCallback(const ReconnectCtlPtr &db_reconnect_ctl)
Invokes the connection's lost connectivity callback.
static ParameterMap parse(const std::string &dbaccess)
Parse database access string.
static bool invokeDbFailedCallback(const ReconnectCtlPtr &db_reconnect_ctl)
Invokes the connection's restore failed connectivity callback.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
static bool invokeDbRecoveredCallback(const ReconnectCtlPtr &db_reconnect_ctl)
Invokes the connection's restored connectivity callback.
Exception thrown on failure to open database.
Exception thrown on failure to execute a database function.
Database duplicate entry error.
Definition: db_exceptions.h:42
Multiple lease records found where one expected.
Definition: db_exceptions.h:28
Common PgSql Connector Pool.
static const char DUPLICATE_KEY[]
Define the PgSql error state for a duplicate key error.
static std::pair< uint32_t, uint32_t > getVersion(const ParameterMap &parameters)
Get the schema version.
Base class for marshalling data to and from PostgreSQL.
static void convertFromBytea(const PgSqlResult &r, const int row, const size_t col, uint8_t *buffer, const size_t buffer_size, size_t &bytes_converted)
Converts a column in a row in a result set to a binary bytes.
static bool isColumnNull(const PgSqlResult &r, const int row, const size_t col)
Returns true if a column within a row is null.
static void getColumnValue(const PgSqlResult &r, const int row, const size_t col, std::string &value)
Fetches text column value as a string.
RAII wrapper for PostgreSQL Result sets.
int getRows() const
Returns the number of rows in the result set.
RAII object representing a PostgreSQL transaction.
void commit()
Commits transaction.
Attempt to modify data in read-only database.
Definition: db_exceptions.h:56
Authentication keys.
Definition: host.h:75
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition: cfgmgr.cc:25
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition: cfgmgr.cc:161
static bool delBackend(const std::string &db_type)
Delete an alternate host backend (aka host data source).
Definition: host_mgr.cc:53
static void addBackend(const std::string &access)
Add an alternate host backend (aka host data source).
Definition: host_mgr.cc:48
static isc::asiolink::IOServicePtr & getIOService()
Returns pointer to the IO service.
Definition: host_mgr.h:643
Wraps value holding size of the page with host reservations.
const size_t page_size_
Holds page size.
Represents a device with IPv4 and/or IPv6 reservations.
Definition: host.h:297
IdentifierType
Type of the host identifier.
Definition: host.h:307
IPv6 reservation for a host.
Definition: host.h:161
const asiolink::IOAddress & getPrefix() const
Returns prefix for the reservation.
Definition: host.h:190
Type getType() const
Returns reservation type.
Definition: host.h:204
Type
Type of the reservation.
Definition: host.h:167
uint8_t getPrefixLen() const
Returns prefix length.
Definition: host.h:195
Option descriptor.
Definition: cfg_option.h:42
OptionPtr option_
Option instance.
Definition: cfg_option.h:45
std::string formatted_value_
Option value in textual (CSV) format.
Definition: cfg_option.h:66
bool persistent_
Persistence flag.
Definition: cfg_option.h:51
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:82
PostgreSQL Host Context Pool.
std::mutex mutex_
The mutex to protect pool access.
std::vector< PgSqlHostContextPtr > pool_
The vector of available contexts.
boost::shared_ptr< PgSqlHostIPv6Exchange > host_ipv46_exchange_
Pointer to an object representing an exchange which can be used to retrieve hosts,...
boost::shared_ptr< PgSqlIPv6ReservationExchange > host_ipv6_reservation_exchange_
Pointer to an object representing an exchange which can be used to insert new IPv6 reservation.
PgSqlHostContext(const DatabaseConnection::ParameterMap &parameters, IOServiceAccessorPtr io_service_accessor, db::DbCallback db_reconnect_callback)
Constructor.
PgSqlConnection conn_
PostgreSQL connection.
boost::shared_ptr< PgSqlHostWithOptionsExchange > host_ipv4_exchange_
The exchange objects are used for transfer of data to/from the database.
bool is_readonly_
Indicates if the database is opened in read only mode.
boost::shared_ptr< PgSqlHostIPv6Exchange > host_ipv6_exchange_
Pointer to an object representing an exchange which can be used to retrieve hosts,...
boost::shared_ptr< PgSqlOptionExchange > host_option_exchange_
Pointer to an object representing an exchange which can be used to insert DHCPv4 or DHCPv6 option int...
Implementation of the PgSqlHostDataSource.
bool ip_reservations_unique_
Holds the setting whether the IP reservations must be unique or may be non-unique.
uint64_t addStatement(PgSqlHostContextPtr &ctx, PgSqlHostDataSourceImpl::StatementIndex stindex, PsqlBindArrayPtr &bind, const bool return_last_id=false)
Executes statements which insert a row into one of the tables.
static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl)
Attempts to reconnect the server to the host DB backend manager.
void checkReadOnly(PgSqlHostContextPtr &ctx) const
Throws exception if database is read only.
DatabaseConnection::ParameterMap parameters_
The parameters.
PgSqlHostContextPoolPtr pool_
The pool of contexts.
void addOption(PgSqlHostContextPtr &ctx, const PgSqlHostDataSourceImpl::StatementIndex &stindex, const OptionDescriptor &opt_desc, const std::string &opt_space, const Optional< SubnetID > &subnet_id, const HostID &host_id)
Inserts a single DHCP option into the database.
void addOptions(PgSqlHostContextPtr &ctx, const StatementIndex &stindex, const ConstCfgOptionPtr &options_cfg, const uint64_t host_id)
Inserts multiple options into the database.
bool delStatement(PgSqlHostContextPtr &ctx, PgSqlHostDataSourceImpl::StatementIndex stindex, PsqlBindArrayPtr &bind)
Executes statements that delete records.
std::pair< uint32_t, uint32_t > getVersion() const
Returns PostgreSQL schema version of the open database.
static const StatementIndex WRITE_STMTS_BEGIN
Index of first statement performing write to the database.
void addResv(PgSqlHostContextPtr &ctx, const IPv6Resrv &resv, const HostID &id)
Inserts IPv6 Reservation into ipv6_reservation table.
std::string timer_name_
Timer name used to register database reconnect timer.
ConstHostPtr getHost(PgSqlHostContextPtr &ctx, const SubnetID &subnet_id, const Host::IdentifierType &identifier_type, const uint8_t *identifier_begin, const size_t identifier_len, StatementIndex stindex, boost::shared_ptr< PgSqlHostExchange > exchange) const
Retrieves a host by subnet and client's unique identifier.
PgSqlHostContextPtr createContext() const
Create a new context.
void getHostCollection(PgSqlHostContextPtr &ctx, StatementIndex stindex, PsqlBindArrayPtr bind, boost::shared_ptr< PgSqlHostExchange > exchange, ConstHostCollection &result, bool single) const
Creates collection of Host objects with associated information such as IPv6 reservations and/or DHCP ...
PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap &parameters)
Constructor.
bool unusable_
Indicates if there is at least one connection that can no longer be used for normal operations.
PgSqlHostContextAlloc(PgSqlHostDataSourceImpl &mgr)
Constructor.
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)
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 bool isUnusable()
Flag which indicates if the host manager has at least one unusable connection.
virtual std::string getName() const
Returns the name of the open database.
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...
virtual std::string getDescription() const
Returns description of the backend.
virtual ConstHostCollection getAllbyHostname4(const std::string &hostname, const SubnetID &subnet_id) const
Return all hosts with a hostname in a DHCPv4 subnet.
virtual ConstHostCollection getPage6(const SubnetID &subnet_id, size_t &source_index, uint64_t lower_host_id, const HostPageSize &page_size) const
Returns range of hosts in a DHCPv6 subnet.
virtual bool del(const SubnetID &subnet_id, const asiolink::IOAddress &addr)
Attempts to delete hosts by (subnet-id, address)
virtual ConstHostCollection getPage4(const SubnetID &subnet_id, size_t &source_index, uint64_t lower_host_id, const HostPageSize &page_size) const
Returns range of hosts in a DHCPv4 subnet.
virtual void rollback()
Rollback Transactions.
virtual std::pair< uint32_t, uint32_t > getVersion() const
Returns backend version.
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)
virtual ConstHostCollection getAll6(const SubnetID &subnet_id) const
Return all hosts in a DHCPv6 subnet.
virtual ConstHostCollection getAllbyHostname6(const std::string &hostname, const SubnetID &subnet_id) const
Return all hosts with a hostname in a DHCPv6 subnet.
virtual void commit()
Commit Transactions.
virtual bool setIPReservationsUnique(const bool unique)
Controls whether IP reservations are unique or non-unique.
virtual ConstHostCollection getAll4(const SubnetID &subnet_id) const
Return all hosts in a DHCPv4 subnet.
virtual isc::db::DatabaseConnection::ParameterMap getParameters() const
Return backend parameters.
PgSqlHostDataSource(const db::DatabaseConnection::ParameterMap &parameters)
Constructor.
virtual void add(const HostPtr &host)
Adds a new host to the collection.
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.
virtual ~PgSqlHostDataSource()
Virtual destructor.
virtual ConstHostCollection getAllbyHostname(const std::string &hostname) const
Return all hosts with a hostname.
static const TimerMgrPtr & instance()
Returns pointer to the sole instance of the TimerMgr.
Definition: timer_mgr.cc:449
RAII class creating a critical section.
A template representing an optional value.
Definition: optional.h:36
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
const size_t OID_INT4
std::function< bool(ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
const size_t OID_INT2
const uint32_t PG_SCHEMA_VERSION_MAJOR
Define PostgreSQL backend version: 6.2.
boost::shared_ptr< IOServiceAccessor > IOServiceAccessorPtr
Pointer to an instance of IOServiceAccessor.
const uint32_t PG_SCHEMA_VERSION_MINOR
const size_t OID_VARCHAR
boost::shared_ptr< PsqlBindArray > PsqlBindArrayPtr
Defines a smart pointer to PsqlBindArray.
const size_t OID_TEXT
const size_t OID_BOOL
const size_t OID_INT8
const size_t OID_BYTEA
std::function< isc::asiolink::IOServicePtr()> IOServiceAccessor
Function which returns the IOService that can be used to recover the connection.
boost::shared_ptr< ReconnectCtl > ReconnectCtlPtr
Pointer to an instance of ReconnectCtl.
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition: dhcpsrv_log.h:56
IPv6ResrvCollection::const_iterator IPv6ResrvIterator
Definition: host.h:241
boost::shared_ptr< PgSqlHostContext > PgSqlHostContextPtr
Type of pointers to contexts.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:706
boost::shared_ptr< Host > HostPtr
Pointer to the Host object.
Definition: host.h:785
std::vector< ConstHostPtr > ConstHostCollection
Collection of the const Host objects.
Definition: host.h:791
boost::shared_ptr< CfgDbAccess > CfgDbAccessPtr
A pointer to the CfgDbAccess.
const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_GET_VERSION
std::pair< IPv6ResrvIterator, IPv6ResrvIterator > IPv6ResrvRange
Definition: host.h:243
const int DHCPSRV_DBG_TRACE_DETAIL
Additional information.
Definition: dhcpsrv_log.h:38
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED
uint32_t SubnetID
Unique identifier for a subnet (both v4 and v6)
Definition: lease.h:24
uint64_t HostID
HostID (used only when storing in MySQL, PostgreSQL or Cassandra)
Definition: host.h:69
boost::shared_ptr< OptionContainer > OptionContainerPtr
Pointer to the OptionContainer object.
Definition: cfg_option.h:272
const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE
boost::shared_ptr< const Host > ConstHostPtr
Const pointer to the Host object.
Definition: host.h:788
const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED
const size_t OPTION_VALUE_MAX_LEN
Maximum length of option value.
Definition: host.h:45
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:24
boost::shared_ptr< PgSqlHostContextPool > PgSqlHostContextPoolPtr
Type of pointers to context pools.
const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_READONLY
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
boost::shared_ptr< const CfgOption > ConstCfgOptionPtr
Const pointer.
Definition: cfg_option.h:709
Definition: edns.h:19
Defines the logger used by the top-level component of kea-lfc.
#define DHCP4_OPTION_SPACE
global std option spaces
#define DHCP6_OPTION_SPACE
data::ConstElementPtr getContext() const
Returns const pointer to the user context.
Definition: user_context.h:24