Kea 2.2.0
pgsql_host_data_source.cc
Go to the documentation of this file.
1// Copyright (C) 2016-2022 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 } else {
224 bind_array->add(host->getIPv4SubnetID());
225 }
226
227 // dhcp6_subnet_id : INT NULL
228 if (host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
229 bind_array->addNull();
230 } else {
231 bind_array->add(host->getIPv6SubnetID());
232 }
233
234 // ipv4_address : BIGINT NULL
235 bind_array->add((host->getIPv4Reservation()));
236
237 // hostname : VARCHAR(255) NULL
238 bind_array->add(host->getHostname());
239
240 // dhcp4_client_classes : VARCHAR(255) NULL
241 // Override default separator to not include space after comma.
242 bind_array->addTempString(host->getClientClasses4().toText(","));
243
244 // dhcp6_client_classes : VARCHAR(255) NULL
245 bind_array->addTempString(host->getClientClasses6().toText(","));
246
247 // user_context: TEXT NULL
248 ConstElementPtr ctx = host->getContext();
249 if (ctx) {
250 std::string user_context_ = ctx->str();
251 bind_array->addTempString(user_context_);
252 } else {
253 bind_array->addNull();
254 }
255
256 // dhcp4_next_server : BIGINT NULL
257 bind_array->add((host->getNextServer()));
258
259 // dhcp4_server_hostname : VARCHAR(64)
260 bind_array->add(host->getServerHostname());
261
262 // dhcp4_boot_file_name : VARCHAR(128)
263 bind_array->add(host->getBootFileName());
264
265 // add auth keys
266 std::string key = host->getKey().toText();
267 if (key.empty()) {
268 bind_array->addNull();
269 } else {
270 bind_array->addTempString(key);
271 }
272
273 // When checking whether the IP is unique we need to bind the IPv4 address
274 // at the end of the query as it has additional binding for the IPv4
275 // address.
276 if (unique_ip) {
277 bind_array->add(host->getIPv4Reservation()); // ipv4_address
278 bind_array->add(host->getIPv4SubnetID()); // subnet_id
279 }
280
281 } catch (const std::exception& ex) {
282 host_.reset();
284 "Could not create bind array from Host: "
285 << host->getHostname() << ", reason: " << ex.what());
286 }
287
288 return (bind_array);
289 };
290
304 virtual void processRowData(ConstHostCollection& hosts,
305 const PgSqlResult& r, int row) {
306 // Peek at the host id , so we can skip it if we already have it
307 // This lets us avoid constructing a copy of host for each
308 // of its sub-rows (options, etc...)
309 HostID row_host_id = getHostId(r, row);
310
311 // Add new host only if there are no hosts or the host id of the
312 // most recently added host is different than the host id of the
313 // currently processed host.
314 if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
315 HostPtr host = retrieveHost(r, row, row_host_id);
316 hosts.push_back(host);
317 }
318 }
319
331 HostPtr retrieveHost(const PgSqlResult& r, int row,
332 const HostID& peeked_host_id = 0) {
333
334 // If the caller peeked ahead at the host_id use that, otherwise
335 // read it from the row.
336 HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
337
338 // dhcp_identifier : BYTEA NOT NULL
339 uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
340 size_t identifier_len;
341 convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
342 sizeof(identifier_value), identifier_len);
343
344 // dhcp_identifier_type : SMALLINT NOT NULL
345 uint8_t type;
346 getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
347 if (type > MAX_IDENTIFIER_TYPE) {
348 isc_throw(BadValue, "invalid dhcp identifier type returned: "
349 << static_cast<int>(type));
350 }
351
352 Host::IdentifierType identifier_type =
353 static_cast<Host::IdentifierType>(type);
354
355 // dhcp4_subnet_id : INT NULL
356 uint32_t subnet_id(SUBNET_ID_UNUSED);
357 if (!isColumnNull(r, row, DHCP4_SUBNET_ID_COL)) {
358 getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
359 }
360 SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
361
362 // dhcp6_subnet_id : INT NULL
363 subnet_id = SUBNET_ID_UNUSED;
364 if (!isColumnNull(r, row, DHCP6_SUBNET_ID_COL)) {
365 getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
366 }
367 SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
368
369 // ipv4_address : BIGINT NULL
370 uint32_t addr4(0);
371 if (!isColumnNull(r, row, IPV4_ADDRESS_COL)) {
372 getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
373 }
374 isc::asiolink::IOAddress ipv4_reservation(addr4);
375
376 // hostname : VARCHAR(255) NULL
377 std::string hostname;
378 if (!isColumnNull(r, row, HOSTNAME_COL)) {
379 getColumnValue(r, row, HOSTNAME_COL, hostname);
380 }
381
382 // dhcp4_client_classes : VARCHAR(255) NULL
383 std::string dhcp4_client_classes;
384 if (!isColumnNull(r, row, DHCP4_CLIENT_CLASSES_COL)) {
385 getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
386 }
387
388 // dhcp6_client_classes : VARCHAR(255) NULL
389 std::string dhcp6_client_classes;
390 if (!isColumnNull(r, row, DHCP6_CLIENT_CLASSES_COL)) {
391 getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
392 }
393
394 // user_context: TEXT
395 std::string user_context;
396 if (!isColumnNull(r, row, USER_CONTEXT_COL)) {
397 getColumnValue(r, row, USER_CONTEXT_COL, user_context);
398 }
399
400 // dhcp4_next_server : BIGINT NULL
401 uint32_t dhcp4_next_server_as_uint32(0);
402 if (!isColumnNull(r, row, DHCP4_NEXT_SERVER_COL)) {
403 getColumnValue(r, row, DHCP4_NEXT_SERVER_COL, dhcp4_next_server_as_uint32);
404 }
405 isc::asiolink::IOAddress dhcp4_next_server(dhcp4_next_server_as_uint32);
406
407 // dhcp4_server_hostname : VARCHAR(64)
408 std::string dhcp4_server_hostname;
409 if (!isColumnNull(r, row, DHCP4_SERVER_HOSTNAME_COL)) {
410 getColumnValue(r, row, DHCP4_SERVER_HOSTNAME_COL, dhcp4_server_hostname);
411 }
412
413 // dhcp4_boot_file_name : VARCHAR(128)
414 std::string dhcp4_boot_file_name;
415 if (!isColumnNull(r, row, DHCP4_BOOT_FILE_NAME_COL)) {
416 getColumnValue(r, row, DHCP4_BOOT_FILE_NAME_COL, dhcp4_boot_file_name);
417 }
418
419 // auth_key : VARCHAR(16)
420 std::string auth_key;
421 if (!isColumnNull(r, row, AUTH_KEY_COL)) {
422 getColumnValue(r, row, AUTH_KEY_COL, auth_key);
423 }
424
425 // Finally, attempt to create the new host.
426 HostPtr host;
427 try {
428 host.reset(new Host(identifier_value, identifier_len,
429 identifier_type, dhcp4_subnet_id,
430 dhcp6_subnet_id, ipv4_reservation, hostname,
431 dhcp4_client_classes, dhcp6_client_classes,
432 dhcp4_next_server, dhcp4_server_hostname,
433 dhcp4_boot_file_name, AuthKey(auth_key)));
434
435 // Set the user context if there is one.
436 if (!user_context.empty()) {
437 try {
438 ConstElementPtr ctx = Element::fromJSON(user_context);
439 if (!ctx || (ctx->getType() != Element::map)) {
440 isc_throw(BadValue, "user context '" << user_context
441 << "' is not a JSON map");
442 }
443 host->setContext(ctx);
444 } catch (const isc::data::JSONError& ex) {
445 isc_throw(BadValue, "user context '" << user_context
446 << "' is invalid JSON: " << ex.what());
447 }
448 }
449
450 host->setHostId(host_id);
451 } catch (const isc::Exception& ex) {
452 isc_throw(DbOperationError, "Could not create host: " << ex.what());
453 }
454
455 return(host);
456 };
457
458protected:
461 HostPtr host_;
462};
463
473class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
474private:
475
477 static const size_t OPTION_COLUMNS = 7;
478
493 class OptionProcessor {
494 public:
495
502 OptionProcessor(const Option::Universe& universe,
503 const size_t start_column)
504 : universe_(universe), start_column_(start_column),
505 option_id_index_(start_column), code_index_(start_column_ + 1),
506 value_index_(start_column_ + 2),
507 formatted_value_index_(start_column_ + 3),
508 space_index_(start_column_ + 4),
509 persistent_index_(start_column_ + 5),
510 user_context_index_(start_column_ + 6),
511 most_recent_option_id_(0) {
512 }
513
518 void clear() {
519 most_recent_option_id_ = 0;
520 }
521
554 void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
555 int row) {
556 // If the option id on this row is NULL, then there's no
557 // option of this type (4/6) on this row to fetch, so bail.
558 if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
559 return;
560 }
561
562 // option_id: INT
563 uint64_t option_id;
564 PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);
565
566 // The row option id must be greater than id if the most recent
567 // option because they are ordered by option id. Otherwise
568 // we assume that we have already processed this option.
569 if (most_recent_option_id_ >= option_id) {
570 return;
571 }
572
573 // Remember current option id as the most recent processed one. We
574 // will be comparing it with option ids in subsequent rows.
575 most_recent_option_id_ = option_id;
576
577 // code: SMALLINT NOT NULL
578 uint16_t code;
579 PgSqlExchange::getColumnValue(r, row, code_index_, code);
580
581 // value: BYTEA
582 uint8_t value[OPTION_VALUE_MAX_LEN];
583 size_t value_len(0);
584 if (!isColumnNull(r, row, value_index_)) {
585 PgSqlExchange::convertFromBytea(r, row, value_index_, value,
586 sizeof(value), value_len);
587 }
588
589 // formatted_value: TEXT
590 std::string formatted_value;
591 if (!isColumnNull(r, row, formatted_value_index_)) {
592 PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
593 formatted_value);
594 }
595
596 // space: VARCHAR(128)
597 std::string space;
598 if (!isColumnNull(r, row, space_index_)) {
599 PgSqlExchange::getColumnValue(r, row, space_index_, space);
600 }
601
602 // If empty or null space provided, use a default top level space.
603 if (space.empty()) {
604 space = (universe_ == Option::V4 ?
606 }
607
608 // persistent: BOOL default false
609 bool persistent;
610 PgSqlExchange::getColumnValue(r, row, persistent_index_,
611 persistent);
612
613 // user_context: TEXT
614 std::string user_context;
615 if (!isColumnNull(r, row, user_context_index_)) {
616 PgSqlExchange::getColumnValue(r, row, user_context_index_,
617 user_context);
618 }
619
620 // Options are held in a binary or textual format in the database.
621 // This is similar to having an option specified in a server
622 // configuration file. Such option is converted to appropriate C++
623 // class, using option definition. Thus, we need to find the
624 // option definition for this option code and option space.
625
626 // If the option space is a standard DHCPv4 or DHCPv6 option space,
627 // this is most likely a standard option, for which we have a
628 // definition created within libdhcp++.
629 OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
630
631 // Otherwise, we may check if this an option encapsulated within the
632 // vendor space.
633 if (!def && (space != DHCP4_OPTION_SPACE) &&
634 (space != DHCP6_OPTION_SPACE)) {
635 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
636 if (vendor_id > 0) {
637 def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
638 code);
639 }
640 }
641
642 // In all other cases, we use runtime option definitions, which
643 // should be also registered within the libdhcp++.
644 if (!def) {
645 def = LibDHCP::getRuntimeOptionDef(space, code);
646 }
647
648 OptionPtr option;
649
650 if (!def) {
651 // If no definition found, we use generic option type.
652 OptionBuffer buf(value, value + value_len);
653 option.reset(new Option(universe_, code, buf.begin(),
654 buf.end()));
655 } else {
656 // The option value may be specified in textual or binary format
657 // in the database. If formatted_value is empty, the binary
658 // format is used. Depending on the format we use a different
659 // variant of the optionFactory function.
660 if (formatted_value.empty()) {
661 OptionBuffer buf(value, value + value_len);
662 option = def->optionFactory(universe_, code, buf.begin(),
663 buf.end());
664 } else {
665 // Spit the value specified in comma separated values
666 // format.
667 std::vector<std::string> split_vec;
668 boost::split(split_vec, formatted_value,
669 boost::is_any_of(","));
670 option = def->optionFactory(universe_, code, split_vec);
671 }
672 }
673
674 OptionDescriptor desc(option, persistent, formatted_value);
675
676 // Set the user context if there is one into the option descriptor.
677 if (!user_context.empty()) {
678 try {
679 ConstElementPtr ctx = Element::fromJSON(user_context);
680 if (!ctx || (ctx->getType() != Element::map)) {
681 isc_throw(BadValue, "user context '" << user_context
682 << "' is no a JSON map");
683 }
684 desc.setContext(ctx);
685 } catch (const isc::data::JSONError& ex) {
686 isc_throw(BadValue, "user context '" << user_context
687 << "' is invalid JSON: " << ex.what());
688 }
689 }
690
691 cfg->add(desc, space);
692 }
693
698 void setColumnNames(std::vector<std::string>& columns) {
699 columns[option_id_index_] = "option_id";
700 columns[code_index_] = "code";
701 columns[value_index_] = "value";
702 columns[formatted_value_index_] = "formatted_value";
703 columns[space_index_] = "space";
704 columns[persistent_index_] = "persistent";
705 columns[user_context_index_] = "user_context";
706 }
707
708 private:
710 Option::Universe universe_;
711
713 size_t start_column_;
714
716
718
719
720 size_t option_id_index_;
721
723 size_t code_index_;
724
726 size_t value_index_;
727
729 size_t formatted_value_index_;
730
732 size_t space_index_;
733
735 size_t persistent_index_;
737
739 size_t user_context_index_;
740
742 uint64_t most_recent_option_id_;
743 };
744
746 typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
747
748public:
749
756 enum FetchedOptions {
757 DHCP4_ONLY,
758 DHCP6_ONLY,
759 DHCP4_AND_DHCP6
760 };
761
770 PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
771 const size_t additional_columns_num = 0)
772 : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
773 + additional_columns_num),
774 opt_proc4_(), opt_proc6_() {
775
776 // Create option processor for DHCPv4 options, if required.
777 if ((fetched_options == DHCP4_ONLY) ||
778 (fetched_options == DHCP4_AND_DHCP6)) {
779 opt_proc4_.reset(new OptionProcessor(Option::V4,
780 findAvailColumn()));
781 opt_proc4_->setColumnNames(columns_);
782 }
783
784 // Create option processor for DHCPv6 options, if required.
785 if ((fetched_options == DHCP6_ONLY) ||
786 (fetched_options == DHCP4_AND_DHCP6)) {
787 opt_proc6_.reset(new OptionProcessor(Option::V6,
788 findAvailColumn()));
789 opt_proc6_->setColumnNames(columns_);
790 }
791 }
792
798 virtual void clear() {
799 PgSqlHostExchange::clear();
800 if (opt_proc4_) {
801 opt_proc4_->clear();
802 }
803
804 if (opt_proc6_) {
805 opt_proc6_->clear();
806 }
807 }
808
818 virtual void processRowData(ConstHostCollection& hosts,
819 const PgSqlResult& r, int row) {
820 HostPtr current_host;
821 if (hosts.empty()) {
822 // Must be the first one, fetch it.
823 current_host = retrieveHost(r, row);
824 hosts.push_back(current_host);
825 } else {
826 // Peek at the host id so we can skip it if we already have
827 // this host. This lets us avoid retrieving the host needlessly
828 // for each of its sub-rows (options, etc...).
829 HostID row_host_id = getHostId(r, row);
830 current_host = boost::const_pointer_cast<Host>(hosts.back());
831
832 // if the row's host id is greater than the one we've been
833 // working on we're starting a new host, so fetch it.
834 if (row_host_id > current_host->getHostId()) {
835 current_host = retrieveHost(r, row, row_host_id);
836 hosts.push_back(current_host);
837 }
838 }
839
840 // Parse DHCPv4 options if required to do so.
841 if (opt_proc4_) {
842 CfgOptionPtr cfg = current_host->getCfgOption4();
843 opt_proc4_->retrieveOption(cfg, r, row);
844 }
845
846 // Parse DHCPv6 options if required to do so.
847 if (opt_proc6_) {
848 CfgOptionPtr cfg = current_host->getCfgOption6();
849 opt_proc6_->retrieveOption(cfg, r, row);
850 }
851 }
852
853private:
854
866 static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
867 return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
868 OPTION_COLUMNS);
869 }
870
874 OptionProcessorPtr opt_proc4_;
875
879 OptionProcessorPtr opt_proc6_;
880};
881
894class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
895private:
896
898 static const size_t RESERVATION_COLUMNS = 5;
899
900public:
901
906 PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
907 : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
908 reservation_id_index_(findAvailColumn()),
909 address_index_(reservation_id_index_ + 1),
910 prefix_len_index_(reservation_id_index_ + 2),
911 type_index_(reservation_id_index_ + 3),
912 iaid_index_(reservation_id_index_ + 4),
913 most_recent_reservation_id_(0) {
914
915 // Provide names of additional columns returned by the queries.
916 columns_[reservation_id_index_] = "reservation_id";
917 columns_[address_index_] = "address";
918 columns_[prefix_len_index_] = "prefix_len";
919 columns_[type_index_] = "type";
920 columns_[iaid_index_] = "dhcp6_iaid";
921
922 BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
923 }
924
930 void clear() {
931 PgSqlHostWithOptionsExchange::clear();
932 most_recent_reservation_id_ = 0;
933 }
934
938 uint64_t getReservationId(const PgSqlResult& r, int row) const {
939 uint64_t resv_id = 0;
940 if (!isColumnNull(r, row, reservation_id_index_)) {
941 getColumnValue(r, row, reservation_id_index_, resv_id);
942 }
943
944 return (resv_id);
945 };
946
951 IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
952
953 // type: SMALLINT NOT NULL
954 uint16_t tmp;
955 getColumnValue(r, row, type_index_, tmp);
956
957 // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
958 IPv6Resrv::Type resv_type;
959 switch (tmp) {
960 case 0:
961 resv_type = IPv6Resrv::TYPE_NA;
962 break;
963
964 case 2:
965 resv_type = IPv6Resrv::TYPE_PD;
966 break;
967
968 default:
970 "invalid IPv6 reservation type returned: "
971 << tmp << ". Only 0 or 2 are allowed.");
972 }
973
974 // address VARCHAR(39) NOT NULL
975 isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));
976
977 // prefix_len: SMALLINT NOT NULL
978 uint16_t prefix_len;
979 getColumnValue(r, row, prefix_len_index_, prefix_len);
980
981 // @todo once we support populating iaid
982 // iaid: INT
983 // int iaid;
984 // getColumnValue(r, row, iaid_index_, iaid);
985
986 // Create the reservation.
987 IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
988 return (reservation);
989 };
990
1012 virtual void processRowData(ConstHostCollection& hosts,
1013 const PgSqlResult& r, int row) {
1014 // Call parent class to fetch host information and options.
1015 PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
1016
1017 // Shouldn't happen but just in case
1018 if (hosts.empty()) {
1019 isc_throw(Unexpected, "no host information while retrieving"
1020 " IPv6 reservation");
1021 }
1022
1023 // If we have reservation id we haven't seen yet, retrieve the
1024 // the reservation, adding it to the current host
1025 uint64_t reservation_id = getReservationId(r, row);
1026 if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
1027 HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
1028 host->addReservation(retrieveReservation(r, row));
1029 most_recent_reservation_id_ = reservation_id;
1030 }
1031 }
1032
1033private:
1035
1036
1037 size_t reservation_id_index_;
1038
1040 size_t address_index_;
1041
1043 size_t prefix_len_index_;
1044
1046 size_t type_index_;
1047
1049 size_t iaid_index_;
1050
1052
1054 uint64_t most_recent_reservation_id_;
1055};
1056
1067class PgSqlIPv6ReservationExchange : public PgSqlExchange {
1068private:
1069
1071 static const size_t RESRV_COLUMNS = 6;
1072
1073public:
1074
1078 PgSqlIPv6ReservationExchange()
1079 : PgSqlExchange(RESRV_COLUMNS),
1080 resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
1081 // Set the column names (for error messages)
1082 columns_[0] = "host_id";
1083 columns_[1] = "address";
1084 columns_[2] = "prefix_len";
1085 columns_[3] = "type";
1086 columns_[4] = "dhcp6_iaid";
1087 BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
1088 }
1089
1104 PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
1105 const HostID& host_id,
1106 const bool unique_ip) {
1107 // Store the values to ensure they remain valid.
1108 // Technically we don't need this, as currently all the values
1109 // are converted to strings and stored by the bind array.
1110 resv_ = resv;
1111
1112 PsqlBindArrayPtr bind_array(new PsqlBindArray());
1113
1114 try {
1115 // address VARCHAR(39) NOT NULL
1116 bind_array->add(resv.getPrefix());
1117
1118 // prefix_len: SMALLINT NOT NULL
1119 bind_array->add(resv.getPrefixLen());
1120
1121 // type: SMALLINT NOT NULL
1122 // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
1123 uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
1124 bind_array->add(type);
1125
1126 // dhcp6_iaid: INT UNSIGNED
1128 bind_array->addNull();
1129
1130 // host_id: BIGINT NOT NULL
1131 bind_array->add(host_id);
1132
1133 // When checking whether the IP is unique we need to bind the IPv6 address
1134 // and prefix length at the end of the query as it has additional binding
1135 // for the IPv6 address and prefix length.
1136 if (unique_ip) {
1137 bind_array->add(resv.getPrefix()); // address
1138 bind_array->add(resv.getPrefixLen()); // prefix_len
1139 }
1140 } catch (const std::exception& ex) {
1142 "Could not create bind array from IPv6 Reservation: "
1143 << resv_.toText() << ", reason: " << ex.what());
1144 }
1145
1146 return (bind_array);
1147 }
1148
1149private:
1151 IPv6Resrv resv_;
1152};
1153
1157class PgSqlOptionExchange : public PgSqlExchange {
1158private:
1159
1160 static const int OPTION_ID_COL = 0;
1161 static const int CODE_COL = 1;
1162 static const int VALUE_COL = 2;
1163 static const int FORMATTED_VALUE_COL = 3;
1164 static const int SPACE_COL = 4;
1165 static const int PERSISTENT_COL = 5;
1166 static const int USER_CONTEXT_COL = 6;
1167 static const int DHCP_CLIENT_CLASS_COL = 7;
1168 static const int DHCP_SUBNET_ID_COL = 8;
1169 static const int HOST_ID_COL = 9;
1170 static const int SCOPE_ID_COL = 10;
1171
1173 static const size_t OPTION_COLUMNS = 11;
1174
1175public:
1176
1178 PgSqlOptionExchange()
1179 : PgSqlExchange(OPTION_COLUMNS), value_(),
1180 value_len_(0), option_() {
1181 columns_[OPTION_ID_COL] = "option_id";
1182 columns_[CODE_COL] = "code";
1183 columns_[VALUE_COL] = "value";
1184 columns_[FORMATTED_VALUE_COL] = "formatted_value";
1185 columns_[SPACE_COL] = "space";
1186 columns_[PERSISTENT_COL] = "persistent";
1187 columns_[USER_CONTEXT_COL] = "user_context";
1188 columns_[DHCP_CLIENT_CLASS_COL] = "dhcp_client_class";
1189 columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
1190 columns_[HOST_ID_COL] = "host_id";
1191 columns_[SCOPE_ID_COL] = "scope_id";
1192
1193 BOOST_STATIC_ASSERT(10 < OPTION_COLUMNS);
1194 }
1195
1204 PsqlBindArrayPtr createBindForSend(const OptionDescriptor& opt_desc,
1205 const std::string& opt_space,
1206 const HostID& host_id) {
1207 // Hold pointer to the option to make sure it remains valid until
1208 // we complete a query.
1209 option_ = opt_desc.option_;
1210
1211 // Create the bind-array
1212 PsqlBindArrayPtr bind_array(new PsqlBindArray());
1213
1214 try {
1215 // option_id: is auto_incremented so skip it
1216
1217 // code: SMALLINT UNSIGNED NOT NULL
1218 bind_array->add(option_->getType());
1219
1220 // value: BYTEA NULL
1221 if (opt_desc.formatted_value_.empty() &&
1222 (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
1223 // The formatted_value is empty and the option value is
1224 // non-empty so we need to prepare on-wire format for the
1225 // option and store it in the database as a BYTEA.
1226 OutputBuffer buf(opt_desc.option_->len());
1227 opt_desc.option_->pack(buf);
1228 const char* buf_ptr = static_cast<const char*>(buf.getData());
1229 value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
1230 buf_ptr + buf.getLength());
1231 value_len_ = value_.size();
1232 bind_array->add(value_);
1233 } else {
1234 // No value or formatted_value specified. In this case, the
1235 // value BYTEA should be NULL.
1236 bind_array->addNull(PsqlBindArray::BINARY_FMT);
1237 }
1238
1239 // formatted_value: TEXT NULL,
1240 if (!opt_desc.formatted_value_.empty()) {
1241 bind_array->addTempString(opt_desc.formatted_value_);
1242 } else {
1243 bind_array->addNull();
1244 }
1245
1246 // space: VARCHAR(128) NULL
1247 if (!opt_space.empty()) {
1248 bind_array->addTempString(opt_space);
1249 } else {
1250 bind_array->addNull();
1251 }
1252
1253 // persistent: BOOLEAN DEFAULT false
1254 bind_array->add(opt_desc.persistent_);
1255
1256 // user_context: TEXT NULL,
1257 ConstElementPtr ctx = opt_desc.getContext();
1258 if (ctx) {
1259 std::string user_context_ = ctx->str();
1260 bind_array->addTempString(user_context_);
1261 } else {
1262 bind_array->addNull();
1263 }
1264
1265 // host_id: INT NULL
1266 if (!host_id) {
1267 isc_throw(BadValue, "host_id cannot be null");
1268 }
1269 bind_array->add(host_id);
1270
1271 } catch (const std::exception& ex) {
1273 "Could not create bind array for inserting DHCP "
1274 "host option: " << option_->toText() << ", reason: "
1275 << ex.what());
1276 }
1277
1278 return (bind_array);
1279 }
1280
1281private:
1282
1284 std::vector<uint8_t> value_;
1285
1287 size_t value_len_;
1288
1290 OptionPtr option_;
1291};
1292
1293} // namespace
1294
1295namespace isc {
1296namespace dhcp {
1297
1308public:
1309
1316 IOServiceAccessorPtr io_service_accessor,
1317 db::DbCallback db_reconnect_callback);
1318
1323
1326 boost::shared_ptr<PgSqlHostWithOptionsExchange> host_ipv4_exchange_;
1327
1330 boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
1331
1335 boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
1336
1339 boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
1340
1344 boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
1345
1348
1351};
1352
1360public:
1361
1363 std::vector<PgSqlHostContextPtr> pool_;
1364
1366 std::mutex mutex_;
1367};
1368
1370typedef boost::shared_ptr<PgSqlHostContextPool> PgSqlHostContextPoolPtr;
1371
1374public:
1375
1385 GET_HOST_DHCPID, // Gets hosts by host identifier
1386 GET_HOST_ADDR, // Gets hosts by IPv4 address
1387 GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
1388 GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
1389 GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
1390 GET_HOST_PREFIX, // Gets host by IPv6 prefix
1391 GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
1392 GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
1393 GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
1394 GET_HOST_HOSTNAME, // Gets hosts by hostname
1395 GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
1396 GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
1397 GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
1398 GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
1399 GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
1400 GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
1401 INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
1402 INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
1403 INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
1404 INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
1405 INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
1406 INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
1407 DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
1408 DEL_HOST_ADDR6, // Delete v6 host (subnet-id, addr6)
1409 DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
1410 DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
1411 NUM_STATEMENTS // Number of statements
1413
1420
1426
1429
1452 static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
1453
1464
1481 uint64_t addStatement(PgSqlHostContextPtr& ctx,
1483 PsqlBindArrayPtr& bind,
1484 const bool return_last_id = false);
1485
1495 PsqlBindArrayPtr& bind);
1496
1502 void addResv(PgSqlHostContextPtr& ctx,
1503 const IPv6Resrv& resv,
1504 const HostID& id);
1505
1517 const OptionDescriptor& opt_desc,
1518 const std::string& opt_space,
1519 const Optional<SubnetID>& subnet_id,
1520 const HostID& host_id);
1521
1531 const StatementIndex& stindex,
1532 const ConstCfgOptionPtr& options_cfg,
1533 const uint64_t host_id);
1534
1554 StatementIndex stindex,
1555 PsqlBindArrayPtr bind,
1556 boost::shared_ptr<PgSqlHostExchange> exchange,
1557 ConstHostCollection& result,
1558 bool single) const;
1559
1578 const SubnetID& subnet_id,
1579 const Host::IdentifierType& identifier_type,
1580 const uint8_t* identifier_begin,
1581 const size_t identifier_len,
1582 StatementIndex stindex,
1583 boost::shared_ptr<PgSqlHostExchange> exchange) const;
1584
1594 void checkReadOnly(PgSqlHostContextPtr& ctx) const;
1595
1604 std::pair<uint32_t, uint32_t> getVersion() const;
1605
1608
1612
1615
1619
1621 std::string timer_name_;
1622};
1623
1624namespace {
1625
1627typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS>
1628TaggedStatementArray;
1629
1632TaggedStatementArray tagged_statements = { {
1633 // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
1634 // Retrieves host information, IPv6 reservations and both DHCPv4 and
1635 // DHCPv6 options associated with the host. The LEFT JOIN clause is used
1636 // to retrieve information from 4 different tables using a single query.
1637 // Hence, this query returns multiple rows for a single host.
1638 {2,
1639 { OID_BYTEA, OID_INT2 },
1640 "get_host_dhcpid",
1641 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1642 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1643 " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1644 " h.user_context, "
1645 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1646 " h.dhcp4_boot_file_name, h.auth_key, "
1647 " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1648 " o4.persistent, o4.user_context, "
1649 " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1650 " o6.persistent, o6.user_context, "
1651 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1652 "FROM hosts AS h "
1653 "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1654 "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1655 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1656 "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
1657 "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1658 },
1659
1660 // PgSqlHostDataSourceImpl::GET_HOST_ADDR
1661 // Retrieves host information along with the DHCPv4 options associated with
1662 // it. Left joining the dhcp4_options table results in multiple rows being
1663 // returned for the same host. The host is retrieved by IPv4 address.
1664 {1,
1665 { OID_INT8 },
1666 "get_host_addr",
1667 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1668 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1669 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1670 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1671 " h.dhcp4_boot_file_name, h.auth_key, "
1672 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1673 " o.persistent, o.user_context "
1674 "FROM hosts AS h "
1675 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1676 "WHERE ipv4_address = $1 "
1677 "ORDER BY h.host_id, o.option_id"
1678 },
1679
1680 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
1681 // Retrieves host information and DHCPv4 options using subnet identifier
1682 // and client's identifier. Left joining the dhcp4_options table results in
1683 // multiple rows being returned for the same host.
1684 {3,
1686 "get_host_subid4_dhcpid",
1687 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1688 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1689 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1690 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1691 " h.dhcp4_boot_file_name, h.auth_key, "
1692 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1693 " o.persistent, o.user_context "
1694 "FROM hosts AS h "
1695 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1696 "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1697 " AND h.dhcp_identifier = $3 "
1698 "ORDER BY h.host_id, o.option_id"
1699 },
1700
1701 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
1702 // Retrieves host information, IPv6 reservations and DHCPv6 options
1703 // associated with a host. The number of rows returned is a multiplication
1704 // of number of IPv6 reservations and DHCPv6 options.
1705 {3,
1707 "get_host_subid6_dhcpid",
1708 "SELECT h.host_id, h.dhcp_identifier, "
1709 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1710 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1711 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1712 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1713 " h.dhcp4_boot_file_name, h.auth_key, "
1714 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1715 " o.persistent, o.user_context, "
1716 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1717 "FROM hosts AS h "
1718 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1719 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1720 "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1721 " AND h.dhcp_identifier = $3 "
1722 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1723 },
1724
1725 // PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
1726 // Retrieves host information and DHCPv4 options for the host using subnet
1727 // identifier and IPv4 reservation. Left joining the dhcp4_options table
1728 // results in multiple rows being returned for the host. The number of
1729 // rows depends on the number of options defined for the host.
1730 {2,
1731 { OID_INT8, OID_INT8 },
1732 "get_host_subid_addr",
1733 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1734 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1735 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1736 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1737 " h.dhcp4_boot_file_name, h.auth_key, "
1738 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1739 " o.persistent, o.user_context "
1740 "FROM hosts AS h "
1741 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1742 "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
1743 "ORDER BY h.host_id, o.option_id"
1744 },
1745
1746 // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
1747 // Retrieves host information, IPv6 reservations and DHCPv6 options
1748 // associated with a host using prefix and prefix length. This query
1749 // returns host information for a single host. However, multiple rows
1750 // are returned due to left joining IPv6 reservations and DHCPv6 options.
1751 // The number of rows returned is multiplication of number of existing
1752 // IPv6 reservations and DHCPv6 options.
1753 {2,
1754 { OID_VARCHAR, OID_INT2 },
1755 "get_host_prefix",
1756 "SELECT h.host_id, h.dhcp_identifier, "
1757 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1758 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1759 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1760 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1761 " h.dhcp4_boot_file_name, h.auth_key, "
1762 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1763 " o.persistent, o.user_context, "
1764 " r.reservation_id, r.address, r.prefix_len, r.type, "
1765 " r.dhcp6_iaid "
1766 "FROM hosts AS h "
1767 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1768 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1769 "WHERE h.host_id = "
1770 " (SELECT host_id FROM ipv6_reservations "
1771 " WHERE address = $1 AND prefix_len = $2) "
1772 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1773 },
1774
1775 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR
1776 // Retrieves host information, IPv6 reservations and DHCPv6 options
1777 // associated with a host using IPv6 subnet id and prefix. This query
1778 // returns host information for a single host. However, multiple rows
1779 // are returned due to left joining IPv6 reservations and DHCPv6 options.
1780 // The number of rows returned is multiplication of number of existing
1781 // IPv6 reservations and DHCPv6 options.
1782 {2,
1783 { OID_INT8, OID_VARCHAR },
1784 "get_host_subid6_addr",
1785 "SELECT h.host_id, h.dhcp_identifier, "
1786 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1787 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1788 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1789 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1790 " h.dhcp4_boot_file_name, h.auth_key, "
1791 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1792 " o.persistent, o.user_context, "
1793 " r.reservation_id, r.address, r.prefix_len, r.type, "
1794 " r.dhcp6_iaid "
1795 "FROM hosts AS h "
1796 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1797 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1798 "WHERE h.dhcp6_subnet_id = $1 AND r.address = $2 "
1799 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1800 },
1801
1802 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4
1803 //
1804 // Retrieves host information for all hosts in a subnet, along with the
1805 // DHCPv4 options associated with it. Left joining the dhcp4_options table
1806 // results in multiple rows being returned for the same host. The hosts are
1807 // retrieved by subnet id.
1808 {1,
1809 { OID_INT8 },
1810 "get_host_subid4",
1811 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1812 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1813 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1814 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1815 " h.dhcp4_boot_file_name, h.auth_key, "
1816 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1817 " o.persistent, o.user_context "
1818 "FROM hosts AS h "
1819 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1820 "WHERE h.dhcp4_subnet_id = $1 "
1821 "ORDER BY h.host_id, o.option_id"
1822 },
1823
1824 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6
1825 //
1826 // Retrieves host information, IPv6 reservations and DHCPv6 options
1827 // associated with all hosts using the IPv6 subnet id. This query returns
1828 // host information for many hosts. However, multiple rows are
1829 // returned due to left joining IPv6 reservations and DHCPv6 options.
1830 // The number of rows returned is multiplication of number of existing
1831 // IPv6 reservations and DHCPv6 options for each host in a subnet. There
1832 // are usually many hosts in a subnet. The amount of returned data may
1833 // be huge.
1834 {1,
1835 { OID_INT8 },
1836 "get_host_subid6",
1837 "SELECT h.host_id, h.dhcp_identifier, "
1838 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1839 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1840 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1841 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1842 " h.dhcp4_boot_file_name, h.auth_key, "
1843 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1844 " o.persistent, o.user_context, "
1845 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1846 "FROM hosts AS h "
1847 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1848 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1849 "WHERE h.dhcp6_subnet_id = $1 "
1850 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1851 },
1852
1853 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME
1854 // Retrieves host information, IPv6 reservations and both DHCPv4 and
1855 // DHCPv6 options associated with all hosts using the hostname.
1856 // The LEFT JOIN clause is used to retrieve information from 4 different
1857 // tables using a single query. Hence, this query returns multiple rows
1858 // for a single host.
1859 {1,
1860 { OID_VARCHAR },
1861 "get_host_hostname",
1862 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1863 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1864 " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1865 " h.user_context, "
1866 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1867 " h.dhcp4_boot_file_name, h.auth_key, "
1868 " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1869 " o4.persistent, o4.user_context, "
1870 " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1871 " o6.persistent, o6.user_context, "
1872 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1873 "FROM hosts AS h "
1874 "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1875 "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1876 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1877 "WHERE lower(h.hostname) = $1 "
1878 "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1879 },
1880
1881 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4
1882 // Retrieves host information for all hosts with a hostname in a subnet,
1883 // along with the DHCPv4 options associated with it. Left joining
1884 // the dhcp4_options table results in multiple rows being returned for
1885 // the same host.
1886 {2,
1887 { OID_VARCHAR, OID_INT8 },
1888 "get_host_hostname_subid4",
1889 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1890 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1891 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1892 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1893 " h.dhcp4_boot_file_name, h.auth_key, "
1894 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1895 " o.persistent, o.user_context "
1896 "FROM hosts AS h "
1897 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1898 "WHERE lower(h.hostname) = $1 AND h.dhcp4_subnet_id = $2 "
1899 "ORDER BY h.host_id, o.option_id"
1900 },
1901
1902 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6
1903 // Retrieves host information, IPv6 reservations and DHCPv6 options
1904 // associated with all hosts using the hostname and the IPv6 subnet id.
1905 // This query returns host information for many hosts. However, multiple
1906 // rows are returned due to left joining IPv6 reservations and DHCPv6
1907 // options. The number of rows returned is multiplication of number of
1908 // existing IPv6 reservations and DHCPv6 options for each host in a subnet.
1909 {2,
1910 { OID_VARCHAR, OID_INT8 },
1911 "get_host_hostname_subid6",
1912 "SELECT h.host_id, h.dhcp_identifier, "
1913 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1914 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1915 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1916 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1917 " h.dhcp4_boot_file_name, h.auth_key, "
1918 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1919 " o.persistent, o.user_context, "
1920 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1921 "FROM hosts AS h "
1922 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1923 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1924 "WHERE lower(h.hostname) = $1 AND h.dhcp6_subnet_id = $2 "
1925 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1926 },
1927
1928 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE
1929 // Retrieves host information along with the DHCPv4 options associated with
1930 // it. Left joining the dhcp4_options table results in multiple rows being
1931 // returned for the same host. The hosts are retrieved by subnet id,
1932 // starting from specified host id. Specified number of hosts is returned.
1933 {3,
1935 "get_host_subid4_page",
1936 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1937 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1938 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1939 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1940 " h.dhcp4_boot_file_name, h.auth_key, "
1941 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1942 " o.persistent, o.user_context "
1943 "FROM ( SELECT * FROM hosts AS h "
1944 " WHERE h.dhcp4_subnet_id = $1 AND h.host_id > $2 "
1945 " ORDER BY h.host_id "
1946 " LIMIT $3 ) AS h "
1947 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1948 "ORDER BY h.host_id, o.option_id"
1949 },
1950
1951 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE
1952 // Retrieves host information, IPv6 reservations and DHCPv6 options
1953 // associated with a host using IPv6 subnet id. This query returns
1954 // host information for a single host. However, multiple rows are
1955 // returned due to left joining IPv6 reservations and DHCPv6 options.
1956 // The number of rows returned is multiplication of number of existing
1957 // IPv6 reservations and DHCPv6 options.
1958 {3,
1960 "get_host_subid6_page",
1961 "SELECT h.host_id, h.dhcp_identifier, "
1962 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1963 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1964 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1965 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1966 " h.dhcp4_boot_file_name, h.auth_key, "
1967 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1968 " o.persistent, o.user_context, "
1969 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1970 "FROM ( SELECT * FROM hosts AS h "
1971 " WHERE h.dhcp6_subnet_id = $1 AND h.host_id > $2 "
1972 " ORDER BY h.host_id "
1973 " LIMIT $3 ) AS h "
1974 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1975 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1976 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1977 },
1978
1979 // PgSqlHostDataSourceImpl::GET_HOST_PAGE4
1980 // Retrieves host information along with the DHCPv4 options associated with
1981 // it. Left joining the dhcp4_options table results in multiple rows being
1982 // returned for the same host. The hosts are retrieved starting from
1983 // specified host id. Specified number of hosts is returned.
1984 {2,
1985 { OID_INT8, OID_INT8 },
1986 "get_host_page4",
1987 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1988 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1989 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1990 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1991 " h.dhcp4_boot_file_name, h.auth_key, "
1992 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1993 " o.persistent, o.user_context "
1994 "FROM ( SELECT * FROM hosts AS h "
1995 " WHERE h.host_id > $1 "
1996 " ORDER BY h.host_id "
1997 " LIMIT $2 ) AS h "
1998 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1999 "ORDER BY h.host_id, o.option_id"
2000 },
2001
2002 // PgSqlHostDataSourceImpl::GET_HOST_PAGE6
2003 // Retrieves host information, IPv6 reservations and DHCPv6 options
2004 // associated with a host using IPv6 subnet id. This query returns
2005 // host information for a single host. However, multiple rows are
2006 // returned due to left joining IPv6 reservations and DHCPv6 options.
2007 // The number of rows returned is multiplication of number of existing
2008 // IPv6 reservations and DHCPv6 options.
2009 {2,
2010 { OID_INT8, OID_INT8 },
2011 "get_host_page6",
2012 "SELECT h.host_id, h.dhcp_identifier, "
2013 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2014 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2015 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2016 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
2017 " h.dhcp4_boot_file_name, h.auth_key, "
2018 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
2019 " o.persistent, o.user_context, "
2020 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
2021 "FROM ( SELECT * FROM hosts AS h "
2022 " WHERE h.host_id > $1 "
2023 " ORDER BY h.host_id "
2024 " LIMIT $2 ) AS h "
2025 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
2026 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
2027 "ORDER BY h.host_id, o.option_id, r.reservation_id"
2028 },
2029
2030 // PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP
2031 // Inserts a host into the 'hosts' table without checking that there is
2032 // a reservation for the IP address.
2033 {13,
2038 "insert_host_non_unique_ip",
2039 "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2040 " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2041 " dhcp4_client_classes, dhcp6_client_classes, user_context, "
2042 " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2043 "VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) "
2044 "RETURNING host_id"
2045 },
2046
2047 // PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP
2048 // Inserts a host into the 'hosts' table with checking that reserved IP
2049 // address is unique. The innermost query checks if there is at least
2050 // one host for the given IP/subnet combination. For checking whether
2051 // hosts exists or not it doesn't matter if we select actual columns,
2052 // thus SELECT 1 was used as an optimization to avoid selecting data
2053 // that will be ignored anyway. If it does not exist the new host is
2054 // inserted. If the host with the given IP address already exists the
2055 // new host won't be inserted. The caller can check the number of
2056 // affected rows to detect that there was a duplicate host in the
2057 // database. Returns the inserted host id.
2058 {15,
2064 "insert_host_unique_ip",
2065 "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2066 " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2067 " dhcp4_client_classes, dhcp6_client_classes, user_context, "
2068 " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2069 " SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
2070 " WHERE NOT EXISTS ("
2071 " SELECT 1 FROM hosts WHERE ipv4_address = $14 AND dhcp4_subnet_id = $15"
2072 " LIMIT 1"
2073 " ) "
2074 "RETURNING host_id"
2075 },
2076
2077 // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE
2078 // Inserts a single IPv6 reservation into 'reservations' table without
2079 // checking that the inserted reservation is unique.
2080 {5,
2082 "insert_v6_resrv_non_unique",
2083 "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2084 " dhcp6_iaid, host_id) "
2085 "VALUES ($1, $2, $3, $4, $5)"
2086 },
2087
2088 // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE
2089 // Inserts a single IPv6 reservation into 'reservations' table with
2090 // checking that the inserted reservation is unique.
2091 {7,
2093 "insert_v6_resrv_unique",
2094 "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2095 " dhcp6_iaid, host_id) "
2096 "SELECT $1, $2, $3, $4, $5 "
2097 " WHERE NOT EXISTS ("
2098 " SELECT 1 FROM ipv6_reservations"
2099 " WHERE address = $6 AND prefix_len = $7"
2100 " LIMIT 1"
2101 " )"
2102 },
2103
2104 // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
2105 // Inserts a single DHCPv4 option into 'dhcp4_options' table.
2106 // Using fixed scope_id = 3, which associates an option with host.
2107 {7,
2110 "insert_v4_host_option",
2111 "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
2112 " persistent, user_context, host_id, scope_id) "
2113 "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2114 },
2115
2116 // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
2117 // Inserts a single DHCPv6 option into 'dhcp6_options' table.
2118 // Using fixed scope_id = 3, which associates an option with host.
2119 {7,
2122 "insert_v6_host_option",
2123 "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
2124 " persistent, user_context, host_id, scope_id) "
2125 "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2126 },
2127
2128 // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
2129 // Deletes a v4 host that matches (subnet-id, addr4)
2130 {2,
2131 { OID_INT8, OID_INT8 },
2132 "del_host_addr4",
2133 "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
2134 },
2135
2136 // PgSqlHostDataSourceImpl::DEL_HOST_ADDR6
2137 // Deletes a v6 host that matches (subnet-id, addr6)
2138 {2,
2139 { OID_INT8, OID_VARCHAR },
2140 "del_host_addr6",
2141 "DELETE FROM hosts USING ipv6_reservations "
2142 " WHERE dhcp6_subnet_id = $1 AND ipv6_reservations.address = $2"
2143 },
2144
2145 // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
2146 // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
2147 {3,
2149 "del_host_subid4_id",
2150 "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
2151 "AND dhcp_identifier_type = $2 "
2152 "AND dhcp_identifier = $3"
2153 },
2154
2155 // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
2156 // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
2157 {3,
2159 "del_host_subid6_id",
2160 "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
2161 "AND dhcp_identifier_type = $2 "
2162 "AND dhcp_identifier = $3"
2163 }
2164}
2165};
2166
2167} // namespace
2168
2169// PgSqlHostContext Constructor
2170
2172 IOServiceAccessorPtr io_service_accessor,
2173 db::DbCallback db_reconnect_callback)
2174 : conn_(parameters, io_service_accessor, db_reconnect_callback),
2175 is_readonly_(true) {
2176}
2177
2178// PgSqlHostContextAlloc Constructor and Destructor
2179
2181 PgSqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
2182
2183 if (MultiThreadingMgr::instance().getMode()) {
2184 // multi-threaded
2185 {
2186 // we need to protect the whole pool_ operation, hence extra scope {}
2187 lock_guard<mutex> lock(mgr_.pool_->mutex_);
2188 if (!mgr_.pool_->pool_.empty()) {
2189 ctx_ = mgr_.pool_->pool_.back();
2190 mgr_.pool_->pool_.pop_back();
2191 }
2192 }
2193 if (!ctx_) {
2194 ctx_ = mgr_.createContext();
2195 }
2196 } else {
2197 // single-threaded
2198 if (mgr_.pool_->pool_.empty()) {
2199 isc_throw(Unexpected, "No available PostgreSQL host context?!");
2200 }
2201 ctx_ = mgr_.pool_->pool_.back();
2202 }
2203}
2204
2206 if (MultiThreadingMgr::instance().getMode()) {
2207 // multi-threaded
2208 lock_guard<mutex> lock(mgr_.pool_->mutex_);
2209 mgr_.pool_->pool_.push_back(ctx_);
2210 if (ctx_->conn_.isUnusable()) {
2211 mgr_.unusable_ = true;
2212 }
2213 } else if (ctx_->conn_.isUnusable()) {
2214 mgr_.unusable_ = true;
2215 }
2216}
2217
2219 : parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
2220 timer_name_("") {
2221
2222 // Create unique timer name per instance.
2223 timer_name_ = "PgSqlHostMgr[";
2224 timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
2225 timer_name_ += "]DbReconnectTimer";
2226
2227 // Check TLS support.
2228 size_t tls(0);
2229 tls += parameters.count("trust-anchor");
2230 tls += parameters.count("cert-file");
2231 tls += parameters.count("key-file");
2232 tls += parameters.count("cipher-list");
2233#ifdef HAVE_PGSQL_SSL
2234 if ((tls > 0) && !PgSqlConnection::warned_about_tls) {
2238 PQinitSSL(1);
2239 }
2240#else
2241 if (tls > 0) {
2244 isc_throw(DbOpenError, "Attempt to configure TLS for PostgreSQL "
2245 << "backend (built with this feature disabled)");
2246 }
2247#endif
2248
2249 // Validate the schema version first.
2250 std::pair<uint32_t, uint32_t> code_version(PGSQL_SCHEMA_VERSION_MAJOR,
2252 std::pair<uint32_t, uint32_t> db_version = getVersion();
2253 if (code_version != db_version) {
2255 "PostgreSQL schema version mismatch: need version: "
2256 << code_version.first << "." << code_version.second
2257 << " found version: " << db_version.first << "."
2258 << db_version.second);
2259 }
2260
2261 // Create an initial context.
2262 pool_.reset(new PgSqlHostContextPool());
2263 pool_->pool_.push_back(createContext());
2264}
2265
2266// Create context.
2267
2273
2274 // Open the database.
2275 ctx->conn_.openDatabase();
2276
2277 // Now prepare the SQL statements.
2278 ctx->conn_.prepareStatements(tagged_statements.begin(),
2279 tagged_statements.begin() + WRITE_STMTS_BEGIN);
2280
2281 // Check if the backend is explicitly configured to operate with
2282 // read only access to the database.
2283 ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
2284
2285 // If we are using read-write mode for the database we also prepare
2286 // statements for INSERTS etc.
2287 if (!ctx->is_readonly_) {
2288 ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
2289 tagged_statements.end());
2290 } else {
2292 }
2293
2294 ctx->host_ipv4_exchange_.reset(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY));
2295 ctx->host_ipv6_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY));
2296 ctx->host_ipv46_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
2297 ctx->host_ipv6_reservation_exchange_.reset(new PgSqlIPv6ReservationExchange());
2298 ctx->host_option_exchange_.reset(new PgSqlOptionExchange());
2299
2300 // Create ReconnectCtl for this connection.
2301 ctx->conn_.makeReconnectCtl(timer_name_);
2302
2303 return (ctx);
2304}
2305
2307}
2308
2309bool
2312
2313 // Invoke application layer connection lost callback.
2314 if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
2315 return (false);
2316 }
2317
2318 bool reopened = false;
2319
2320 const std::string timer_name = db_reconnect_ctl->timerName();
2321
2322 // At least one connection was lost.
2323 try {
2324 CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
2325 std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
2326 for (std::string& hds : host_db_access_list) {
2327 auto parameters = DatabaseConnection::parse(hds);
2328 if (HostMgr::delBackend("postgresql", hds, true)) {
2330 }
2331 }
2332 reopened = true;
2333 } catch (const std::exception& ex) {
2335 .arg(ex.what());
2336 }
2337
2338 if (reopened) {
2339 // Cancel the timer.
2340 if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2341 TimerMgr::instance()->unregisterTimer(timer_name);
2342 }
2343
2344 // Invoke application layer connection recovered callback.
2345 if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
2346 return (false);
2347 }
2348 } else {
2349 if (!db_reconnect_ctl->checkRetries()) {
2350 // We're out of retries, log it and initiate shutdown.
2352 .arg(db_reconnect_ctl->maxRetries());
2353
2354 // Cancel the timer.
2355 if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2356 TimerMgr::instance()->unregisterTimer(timer_name);
2357 }
2358
2359 // Invoke application layer connection failed callback.
2361 return (false);
2362 }
2363
2365 .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
2366 .arg(db_reconnect_ctl->maxRetries())
2367 .arg(db_reconnect_ctl->retryInterval());
2368
2369 // Start the timer.
2370 if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
2371 TimerMgr::instance()->registerTimer(timer_name,
2372 std::bind(&PgSqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
2373 db_reconnect_ctl->retryInterval(),
2375 }
2376 TimerMgr::instance()->setup(timer_name);
2377 }
2378
2379 return (true);
2380}
2381
2382uint64_t
2384 StatementIndex stindex,
2385 PsqlBindArrayPtr& bind_array,
2386 const bool return_last_id) {
2387 uint64_t last_id = 0;
2388 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2389 tagged_statements[stindex].nbparams,
2390 &bind_array->values_[0],
2391 &bind_array->lengths_[0],
2392 &bind_array->formats_[0], 0));
2393
2394 int s = PQresultStatus(r);
2395
2396 if (s != PGRES_COMMAND_OK) {
2397 // Failure: check for the special case of duplicate entry.
2398 if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
2399 isc_throw(DuplicateEntry, "Database duplicate entry error");
2400 }
2401
2402 // Connection determines if the error is fatal or not, and
2403 // throws the appropriate exception
2404 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2405 }
2406
2407 // Get the number of affected rows.
2408 char* rows_affected = PQcmdTuples(r);
2409 if (!rows_affected) {
2411 "Could not retrieve the number of affected rows.");
2412 }
2413
2414 // If the number of rows inserted is 0 it means that the query detected
2415 // an attempt to insert duplicated data for which there is no unique
2416 // index in the database. Unique indexes are not created in the database
2417 // when it may be sometimes allowed to insert duplicated records per
2418 // server's configuration.
2419 if (rows_affected[0] == '0') {
2420 isc_throw(DuplicateEntry, "Database duplicate entry error");
2421 }
2422
2423 if (return_last_id) {
2424 PgSqlExchange::getColumnValue(r, 0, 0, last_id);
2425 }
2426
2427 return (last_id);
2428}
2429
2430bool
2432 StatementIndex stindex,
2433 PsqlBindArrayPtr& bind_array) {
2434 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2435 tagged_statements[stindex].nbparams,
2436 &bind_array->values_[0],
2437 &bind_array->lengths_[0],
2438 &bind_array->formats_[0], 0));
2439
2440 int s = PQresultStatus(r);
2441
2442 if (s != PGRES_COMMAND_OK) {
2443 // Connection determines if the error is fatal or not, and
2444 // throws the appropriate exception
2445 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2446 }
2447
2448 // Now check how many rows (hosts) were deleted. This should be either
2449 // "0" or "1".
2450 char* rows_deleted = PQcmdTuples(r);
2451 if (!rows_deleted) {
2453 "Could not retrieve the number of deleted rows.");
2454 }
2455 return (rows_deleted[0] != '0');
2456}
2457
2458void
2460 const IPv6Resrv& resv,
2461 const HostID& id) {
2462 PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->
2463 createBindForSend(resv, id, ip_reservations_unique_);
2464
2465 addStatement(ctx,
2467 bind_array);
2468}
2469
2470void
2472 const StatementIndex& stindex,
2473 const OptionDescriptor& opt_desc,
2474 const std::string& opt_space,
2475 const Optional<SubnetID>&,
2476 const HostID& id) {
2477 PsqlBindArrayPtr bind_array = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, id);
2478
2479 addStatement(ctx, stindex, bind_array);
2480}
2481
2482void
2484 const StatementIndex& stindex,
2485 const ConstCfgOptionPtr& options_cfg,
2486 const uint64_t host_id) {
2487 // Get option space names and vendor space names and combine them within a
2488 // single list.
2489 std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
2490 std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
2491 option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
2492 vendor_spaces.end());
2493
2494 // For each option space retrieve all options and insert them into the
2495 // database.
2496 for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
2497 OptionContainerPtr options = options_cfg->getAll(*space);
2498 if (options && !options->empty()) {
2499 for (auto opt = options->begin(); opt != options->end(); ++opt) {
2500 addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
2501 }
2502 }
2503 }
2504}
2505
2506void
2508 StatementIndex stindex,
2509 PsqlBindArrayPtr bind_array,
2510 boost::shared_ptr<PgSqlHostExchange> exchange,
2511 ConstHostCollection& result,
2512 bool single) const {
2513
2514 exchange->clear();
2515 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2516 tagged_statements[stindex].nbparams,
2517 &bind_array->values_[0],
2518 &bind_array->lengths_[0],
2519 &bind_array->formats_[0], 0));
2520
2521 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2522
2523 int rows = r.getRows();
2524 for (int row = 0; row < rows; ++row) {
2525 exchange->processRowData(result, r, row);
2526
2527 if (single && result.size() > 1) {
2528 isc_throw(MultipleRecords, "multiple records were found in the "
2529 "database where only one was expected for query "
2530 << tagged_statements[stindex].name);
2531 }
2532 }
2533}
2534
2537 const SubnetID& subnet_id,
2538 const Host::IdentifierType& identifier_type,
2539 const uint8_t* identifier_begin,
2540 const size_t identifier_len,
2541 StatementIndex stindex,
2542 boost::shared_ptr<PgSqlHostExchange> exchange) const {
2543
2544 // Set up the WHERE clause value
2545 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2546
2547 // Add the subnet id.
2548 bind_array->add(subnet_id);
2549
2550 // Add the Identifier type.
2551 bind_array->add(static_cast<uint8_t>(identifier_type));
2552
2553 // Add the identifier value.
2554 bind_array->add(identifier_begin, identifier_len);
2555
2556 ConstHostCollection collection;
2557 getHostCollection(ctx, stindex, bind_array, exchange, collection, true);
2558
2559 // Return single record if present, else clear the host.
2560 ConstHostPtr result;
2561 if (!collection.empty()) {
2562 result = *collection.begin();
2563 }
2564
2565 return (result);
2566}
2567
2568std::pair<uint32_t, uint32_t>
2573}
2574
2575void
2577 if (ctx->is_readonly_) {
2578 isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
2579 " to operate in read only mode");
2580 }
2581}
2582
2583/*********** PgSqlHostDataSource *********************/
2584
2586 : impl_(new PgSqlHostDataSourceImpl(parameters)) {
2587}
2588
2590}
2591
2594 return (impl_->parameters_);
2595}
2596
2597void
2599 // Get a context
2600 PgSqlHostContextAlloc get_context(*impl_);
2601 PgSqlHostContextPtr ctx = get_context.ctx_;
2602
2603 // If operating in read-only mode, throw exception.
2604 impl_->checkReadOnly(ctx);
2605
2606 // Initiate PostgreSQL transaction as we will have to make multiple queries
2607 // to insert host information into multiple tables. If that fails on
2608 // any stage, the transaction will be rolled back by the destructor of
2609 // the PgSqlTransaction class.
2610 PgSqlTransaction transaction(ctx->conn_);
2611
2612 // If we're configured to check that an IP reservation within a given subnet
2613 // is unique, the IP reservation exists and the subnet is actually set
2614 // we will be using a special query that checks for uniqueness. Otherwise,
2615 // we will use a regular insert statement.
2616 bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
2617 && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
2618
2619 // Create the PgSQL Bind array for the host
2620 PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
2621
2622 // ... and insert the host.
2623 uint32_t host_id = impl_->addStatement(ctx,
2626 bind_array, true);
2627
2628 // Insert DHCPv4 options.
2629 ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
2630 if (cfg_option4) {
2632 cfg_option4, host_id);
2633 }
2634
2635 // Insert DHCPv6 options.
2636 ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
2637 if (cfg_option6) {
2639 cfg_option6, host_id);
2640 }
2641
2642 // Insert IPv6 reservations.
2643 IPv6ResrvRange v6resv = host->getIPv6Reservations();
2644 if (std::distance(v6resv.first, v6resv.second) > 0) {
2645 for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
2646 ++resv) {
2647 impl_->addResv(ctx, resv->second, host_id);
2648 }
2649 }
2650
2651 // Everything went fine, so explicitly commit the transaction.
2652 transaction.commit();
2653}
2654
2655bool
2657 const asiolink::IOAddress& addr) {
2658 // Get a context
2659 PgSqlHostContextAlloc get_context(*impl_);
2660 PgSqlHostContextPtr ctx = get_context.ctx_;
2661
2662 // If operating in read-only mode, throw exception.
2663 impl_->checkReadOnly(ctx);
2664
2665 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2666 bind_array->add(subnet_id);
2667
2668 // v4
2669 if (addr.isV4()) {
2670 bind_array->add(addr);
2671 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
2672 bind_array));
2673 }
2674
2675 // v6
2676 bind_array->addTempString(addr.toText());
2677
2678 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR6,
2679 bind_array));
2680}
2681
2682bool
2684 const Host::IdentifierType& identifier_type,
2685 const uint8_t* identifier_begin,
2686 const size_t identifier_len) {
2687 // Get a context
2688 PgSqlHostContextAlloc get_context(*impl_);
2689 PgSqlHostContextPtr ctx = get_context.ctx_;
2690
2691 // If operating in read-only mode, throw exception.
2692 impl_->checkReadOnly(ctx);
2693
2694 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2695
2696 // Subnet-id
2697 bind_array->add(subnet_id);
2698
2699 // identifier-type
2700 bind_array->add(static_cast<uint8_t>(identifier_type));
2701
2702 // identifier
2703 bind_array->add(identifier_begin, identifier_len);
2704
2705 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
2706 bind_array));
2707}
2708
2709bool
2711 const Host::IdentifierType& identifier_type,
2712 const uint8_t* identifier_begin,
2713 const size_t identifier_len) {
2714 // Get a context
2715 PgSqlHostContextAlloc get_context(*impl_);
2716 PgSqlHostContextPtr ctx = get_context.ctx_;
2717
2718 // If operating in read-only mode, throw exception.
2719 impl_->checkReadOnly(ctx);
2720
2721 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2722
2723 // Subnet-id
2724 bind_array->add(subnet_id);
2725
2726 // identifier-type
2727 bind_array->add(static_cast<uint8_t>(identifier_type));
2728
2729 // identifier
2730 bind_array->add(identifier_begin, identifier_len);
2731
2732 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
2733 bind_array));
2734}
2735
2738 const uint8_t* identifier_begin,
2739 const size_t identifier_len) const {
2740 // Get a context
2741 PgSqlHostContextAlloc get_context(*impl_);
2742 PgSqlHostContextPtr ctx = get_context.ctx_;
2743
2744 // Set up the WHERE clause value
2745 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2746
2747 // Identifier value.
2748 bind_array->add(identifier_begin, identifier_len);
2749
2750 // Identifier type.
2751 bind_array->add(static_cast<uint8_t>(identifier_type));
2752
2753 ConstHostCollection result;
2754 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
2755 bind_array, ctx->host_ipv46_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_SUBID4,
2774 bind_array, ctx->host_ipv4_exchange_, result, false);
2775
2776 return (result);
2777}
2778
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 subnet id.
2789 bind_array->add(subnet_id);
2790
2791 ConstHostCollection result;
2792 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6,
2793 bind_array, ctx->host_ipv6_exchange_, result, false);
2794
2795 return (result);
2796}
2797
2799PgSqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
2800 // Get a context
2801 PgSqlHostContextAlloc get_context(*impl_);
2802 PgSqlHostContextPtr ctx = get_context.ctx_;
2803
2804 // Set up the WHERE clause value
2805 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2806
2807 // Add the hostname.
2808 bind_array->add(hostname);
2809
2810 ConstHostCollection result;
2811 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME,
2812 bind_array, ctx->host_ipv46_exchange_, result, false);
2813
2814 return (result);
2815}
2816
2818PgSqlHostDataSource::getAllbyHostname4(const std::string& hostname,
2819 const SubnetID& subnet_id) const {
2820 // Get a context
2821 PgSqlHostContextAlloc get_context(*impl_);
2822 PgSqlHostContextPtr ctx = get_context.ctx_;
2823
2824 // Set up the WHERE clause value
2825 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2826
2827 // Add the hostname.
2828 bind_array->add(hostname);
2829
2830 // Add the subnet id.
2831 bind_array->add(subnet_id);
2832
2833 ConstHostCollection result;
2834 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
2835 bind_array, ctx->host_ipv4_exchange_, result, false);
2836
2837 return (result);
2838}
2839
2841PgSqlHostDataSource::getAllbyHostname6(const std::string& hostname,
2842 const SubnetID& subnet_id) const {
2843 // Get a context
2844 PgSqlHostContextAlloc get_context(*impl_);
2845 PgSqlHostContextPtr ctx = get_context.ctx_;
2846
2847 // Set up the WHERE clause value
2848 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2849
2850 // Add the hostname.
2851 bind_array->add(hostname);
2852
2853 // Add the subnet id.
2854 bind_array->add(subnet_id);
2855
2856 ConstHostCollection result;
2857 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
2858 bind_array, ctx->host_ipv6_exchange_, result, false);
2859
2860 return (result);
2861}
2862
2865 size_t& /*source_index*/,
2866 uint64_t lower_host_id,
2867 const HostPageSize& page_size) const {
2868 // Get a context
2869 PgSqlHostContextAlloc get_context(*impl_);
2870 PgSqlHostContextPtr ctx = get_context.ctx_;
2871
2872 // Set up the WHERE clause value
2873 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2874
2875 // Add the subnet id.
2876 bind_array->add(subnet_id);
2877
2878 // Add the lower bound host id.
2879 bind_array->add(lower_host_id);
2880
2881 // Add the page size value.
2882 string page_size_data =
2883 boost::lexical_cast<std::string>(page_size.page_size_);
2884 bind_array->add(page_size_data);
2885
2886 ConstHostCollection result;
2887 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
2888 bind_array, ctx->host_ipv4_exchange_, result, false);
2889
2890 return (result);
2891}
2892
2895 size_t& /*source_index*/,
2896 uint64_t lower_host_id,
2897 const HostPageSize& page_size) const {
2898 // Get a context
2899 PgSqlHostContextAlloc get_context(*impl_);
2900 PgSqlHostContextPtr ctx = get_context.ctx_;
2901
2902 // Set up the WHERE clause value
2903 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2904
2905 // Add the subnet id.
2906 bind_array->add(subnet_id);
2907
2908 // Add the lower bound host id.
2909 bind_array->add(lower_host_id);
2910
2911 // Add the page size value.
2912 string page_size_data =
2913 boost::lexical_cast<std::string>(page_size.page_size_);
2914 bind_array->add(page_size_data);
2915
2916 ConstHostCollection result;
2917 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
2918 bind_array, ctx->host_ipv6_exchange_, result, false);
2919
2920 return (result);
2921}
2922
2924PgSqlHostDataSource::getPage4(size_t& /*source_index*/,
2925 uint64_t lower_host_id,
2926 const HostPageSize& page_size) const {
2927 // Get a context
2928 PgSqlHostContextAlloc get_context(*impl_);
2929 PgSqlHostContextPtr ctx = get_context.ctx_;
2930
2931 // Set up the WHERE clause value
2932 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2933
2934 // Add the lower bound host id.
2935 bind_array->add(lower_host_id);
2936
2937 // Add the page size value.
2938 string page_size_data =
2939 boost::lexical_cast<std::string>(page_size.page_size_);
2940 bind_array->add(page_size_data);
2941
2942 ConstHostCollection result;
2943 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE4,
2944 bind_array, ctx->host_ipv4_exchange_, result, false);
2945
2946 return (result);
2947}
2948
2950PgSqlHostDataSource::getPage6(size_t& /*source_index*/,
2951 uint64_t lower_host_id,
2952 const HostPageSize& page_size) const {
2953 // Get a context
2954 PgSqlHostContextAlloc get_context(*impl_);
2955 PgSqlHostContextPtr ctx = get_context.ctx_;
2956
2957 // Set up the WHERE clause value
2958 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2959
2960 // Add the lower bound host id.
2961 bind_array->add(lower_host_id);
2962
2963 // Add the page size value.
2964 string page_size_data =
2965 boost::lexical_cast<std::string>(page_size.page_size_);
2966 bind_array->add(page_size_data);
2967
2968 ConstHostCollection result;
2969 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE6,
2970 bind_array, ctx->host_ipv6_exchange_, result, false);
2971
2972 return (result);
2973}
2974
2977 // Get a context
2978 PgSqlHostContextAlloc get_context(*impl_);
2979 PgSqlHostContextPtr ctx = get_context.ctx_;
2980
2981 // Set up the WHERE clause value
2982 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2983
2984 // v4 Reservation address
2985 bind_array->add(address);
2986
2987 ConstHostCollection result;
2988 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_ADDR,
2989 bind_array, ctx->host_ipv4_exchange_, result, false);
2990
2991 return (result);
2992}
2993
2996 const Host::IdentifierType& identifier_type,
2997 const uint8_t* identifier_begin,
2998 const size_t identifier_len) const {
2999 // Get a context
3000 PgSqlHostContextAlloc get_context(*impl_);
3001 PgSqlHostContextPtr ctx = get_context.ctx_;
3002
3003 return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
3005 ctx->host_ipv4_exchange_));
3006}
3007
3010 const asiolink::IOAddress& address) const {
3011 // Get a context
3012 PgSqlHostContextAlloc get_context(*impl_);
3013 PgSqlHostContextPtr ctx = get_context.ctx_;
3014
3015 if (!address.isV4()) {
3016 isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
3017 " wrong address type, address supplied is an IPv6 address");
3018 }
3019
3020 // Set up the WHERE clause value
3021 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3022
3023 // Add the subnet id
3024 bind_array->add(subnet_id);
3025
3026 // Add the address
3027 bind_array->add(address);
3028
3029 ConstHostCollection collection;
3030 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3031 bind_array, ctx->host_ipv4_exchange_, collection, true);
3032
3033 // Return single record if present, else clear the host.
3034 ConstHostPtr result;
3035 if (!collection.empty()) {
3036 result = *collection.begin();
3037 }
3038
3039 return (result);
3040}
3041
3044 const asiolink::IOAddress& address) const {
3045 // Get a context
3046 PgSqlHostContextAlloc get_context(*impl_);
3047 PgSqlHostContextPtr ctx = get_context.ctx_;
3048
3049 if (!address.isV4()) {
3050 isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
3051 " wrong address type, address supplied is an IPv6 address");
3052 }
3053
3054 // Set up the WHERE clause value
3055 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3056
3057 // Add the subnet id
3058 bind_array->add(subnet_id);
3059
3060 // Add the address
3061 bind_array->add(address);
3062
3063 ConstHostCollection collection;
3064 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3065 bind_array, ctx->host_ipv4_exchange_, collection, false);
3066 return (collection);
3067}
3068
3071 const Host::IdentifierType& identifier_type,
3072 const uint8_t* identifier_begin,
3073 const size_t identifier_len) const {
3074 // Get a context
3075 PgSqlHostContextAlloc get_context(*impl_);
3076 PgSqlHostContextPtr ctx = get_context.ctx_;
3077
3078 return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
3080 ctx->host_ipv6_exchange_));
3081}
3082
3085 const uint8_t prefix_len) const {
3086 if (!prefix.isV6()) {
3087 isc_throw(BadValue, "PgSqlHostDataSource::get6(prefix, prefix_len): "
3088 "wrong address type, address supplied is an IPv4 address");
3089 }
3090
3091 // Get a context
3092 PgSqlHostContextAlloc get_context(*impl_);
3093 PgSqlHostContextPtr ctx = get_context.ctx_;
3094
3095 // Set up the WHERE clause value
3096 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3097
3098 // Add the prefix
3099 bind_array->add(prefix);
3100
3101 // Add the prefix length
3102 bind_array->add(prefix_len);
3103
3104 ConstHostCollection collection;
3105 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
3106 bind_array, ctx->host_ipv6_exchange_, collection, true);
3107
3108 // Return single record if present, else clear the host.
3109 ConstHostPtr result;
3110 if (!collection.empty()) {
3111 result = *collection.begin();
3112 }
3113
3114 return (result);
3115}
3116
3119 const asiolink::IOAddress& address) const {
3120 if (!address.isV6()) {
3121 isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3122 "wrong address type, address supplied is an IPv4 address");
3123 }
3124
3125 // Get a context
3126 PgSqlHostContextAlloc get_context(*impl_);
3127 PgSqlHostContextPtr ctx = get_context.ctx_;
3128
3129 // Set up the WHERE clause value
3130 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3131
3132 // Add the subnet id
3133 bind_array->add(subnet_id);
3134
3135 // Add the prefix
3136 bind_array->add(address);
3137
3138 ConstHostCollection collection;
3139 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3140 bind_array, ctx->host_ipv6_exchange_, collection, true);
3141
3142 // Return single record if present, else clear the host.
3143 ConstHostPtr result;
3144 if (!collection.empty()) {
3145 result = *collection.begin();
3146 }
3147
3148 return (result);
3149}
3150
3153 const asiolink::IOAddress& address) const {
3154 if (!address.isV6()) {
3155 isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3156 "wrong address type, address supplied is an IPv4 address");
3157 }
3158
3159 // Get a context
3160 PgSqlHostContextAlloc get_context(*impl_);
3161 PgSqlHostContextPtr ctx = get_context.ctx_;
3162
3163 // Set up the WHERE clause value
3164 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3165
3166 // Add the subnet id
3167 bind_array->add(subnet_id);
3168
3169 // Add the prefix
3170 bind_array->add(address);
3171
3172 ConstHostCollection collection;
3173 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3174 bind_array, ctx->host_ipv6_exchange_, collection, false);
3175 return (collection);
3176}
3177
3178// Miscellaneous database methods.
3179
3180std::string
3182 std::string name = "";
3183 // Get a context
3184 PgSqlHostContextAlloc get_context(*impl_);
3185 PgSqlHostContextPtr ctx = get_context.ctx_;
3186
3187 try {
3188 name = ctx->conn_.getParameter("name");
3189 } catch (...) {
3190 // Return an empty name
3191 }
3192 return (name);
3193}
3194
3195std::string
3197 return (std::string("Host data source that stores host information"
3198 "in PostgreSQL database"));
3199}
3200
3201std::pair<uint32_t, uint32_t>
3203 return(impl_->getVersion());
3204}
3205
3206void
3208 // Get a context
3209 PgSqlHostContextAlloc get_context(*impl_);
3210 PgSqlHostContextPtr ctx = get_context.ctx_;
3211
3212 // If operating in read-only mode, throw exception.
3213 impl_->checkReadOnly(ctx);
3214 ctx->conn_.commit();
3215}
3216
3217void
3219 // Get a context
3220 PgSqlHostContextAlloc get_context(*impl_);
3221 PgSqlHostContextPtr ctx = get_context.ctx_;
3222
3223 // If operating in read-only mode, throw exception.
3224 impl_->checkReadOnly(ctx);
3225 ctx->conn_.rollback();
3226}
3227
3228bool
3230 impl_->ip_reservations_unique_ = unique;
3231 return (true);
3232}
3233
3234bool
3236 return (impl_->unusable_);
3237}
3238
3239} // namespace dhcp
3240} // 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 util::ReconnectCtlPtr &db_reconnect_ctl)
Invokes the connection's lost connectivity callback.
static std::string redactedAccessString(const ParameterMap &parameters)
Redact database access string.
static bool invokeDbFailedCallback(const util::ReconnectCtlPtr &db_reconnect_ctl)
Invokes the connection's restore failed connectivity callback.
static ParameterMap parse(const std::string &dbaccess)
Parse database access string.
static bool invokeDbRecoveredCallback(const util::ReconnectCtlPtr &db_reconnect_ctl)
Invokes the connection's restored connectivity callback.
std::map< std::string, std::string > ParameterMap
Database configuration parameter map.
Exception thrown on failure to open database.
Exception thrown on failure to execute a database function.
Database duplicate entry error.
Definition: db_exceptions.h:30
Multiple lease records found where one expected.
Definition: db_exceptions.h:16
Common PgSql Connector Pool.
static bool warned_about_tls
Emit the TLS support warning only once.
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:44
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:83
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
const size_t OID_INT2
boost::shared_ptr< IOServiceAccessor > IOServiceAccessorPtr
Pointer to an instance of IOServiceAccessor.
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 uint32_t PGSQL_SCHEMA_VERSION_MINOR
const size_t OID_INT8
const size_t OID_BYTEA
std::function< bool(util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback
Defines a callback prototype for propagating events upward.
std::function< isc::asiolink::IOServicePtr()> IOServiceAccessor
Function which returns the IOService that can be used to recover the connection.
const uint32_t PGSQL_SCHEMA_VERSION_MAJOR
Define the PostgreSQL backend version.
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
const isc::log::MessageID DHCPSRV_PGSQL_NO_TLS_SUPPORT
boost::shared_ptr< OptionDefinition > OptionDefinitionPtr
Pointer to option definition object.
const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED
const isc::log::MessageID DHCPSRV_PGSQL_TLS_SUPPORT
uint32_t SubnetID
Defines unique IPv4 or IPv6 subnet identifier.
Definition: subnet_id.h:24
uint64_t HostID
HostID (used only when storing in MySQL or PostgreSQL backends)
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
boost::shared_ptr< ReconnectCtl > ReconnectCtlPtr
Pointer to an instance of ReconnectCtl.
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