1 // Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <asiolink/io_service.h>
10 #include <database/db_exceptions.h>
11 #include <dhcp/libdhcp++.h>
12 #include <dhcp/option.h>
13 #include <dhcp/option_definition.h>
14 #include <dhcp/option_space.h>
15 #include <dhcpsrv/cfg_db_access.h>
16 #include <dhcpsrv/cfg_option.h>
17 #include <dhcpsrv/cfgmgr.h>
18 #include <dhcpsrv/dhcpsrv_log.h>
19 #include <dhcpsrv/host_mgr.h>
20 #include <dhcpsrv/pgsql_host_data_source.h>
21 #include <dhcpsrv/timer_mgr.h>
22 #include <util/buffer.h>
23 #include <util/multi_threading_mgr.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
37 using namespace isc;
38 using namespace isc::asiolink;
39 using namespace isc::db;
40 using namespace isc::dhcp;
41 using namespace isc::util;
42 using namespace isc::data;
43 using namespace std;
44
45 namespace {
46
47 /// @brief Maximum length of option value.
48 /// The maximum size of the raw option data that may be read from the
49 /// database.
50 const size_t OPTION_VALUE_MAX_LEN = 4096;
51
52 /// @brief Numeric value representing last supported identifier.
53 ///
54 /// This value is used to validate whether the identifier type stored in
55 /// a database is within bounds. of supported identifiers.
56 const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
57
58 /// @brief Maximum length of DHCP identifier value.
59 const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
60
61 /// @brief This class provides mechanisms for sending and retrieving
62 /// information from the 'hosts' table.
63 ///
64 /// This class is used to insert and retrieve entries from the 'hosts' table.
65 /// The queries used with this class do not retrieve IPv6 reservations or
66 /// options associated with a host to minimize impact on performance. Other
67 /// classes derived from @ref PgSqlHostExchange should be used to retrieve
68 /// information about IPv6 reservations and options.
69 ///
70 /// Database schema contains several unique indexes to guard against adding
71 /// multiple hosts for the same client identifier in a single subnet and for
72 /// adding multiple hosts with a reservation for the same IPv4 address in a
73 /// single subnet. The exceptions that have to be taken into account are
74 /// listed below:
75 /// - zero or null IPv4 address indicates that there is no reservation for the
76 /// IPv4 address for the host,
77 /// - null subnet identifier (either IPv4 or IPv6) indicates that
78 /// this subnet identifier must be ignored. Specifically, this is the case
79 /// when host reservation is created for the DHCPv4 server, the IPv6 subnet id
80 /// should be ignored. Conversely, when host reservation is created for the
81 /// DHCPv6 server, the IPv4 subnet id should be ignored.
82 /// NOTE! Zero is the "global" subnet id as Kea 1.5.0
83 ///
84 /// To exclude those special case values, the Postgres backend uses partial
85 /// indexes, i.e. the only values that are included in the index are those that
86 /// are non-zero and non-null.
87 class PgSqlHostExchange : public PgSqlExchange {
88 private:
89
90 /// @brief Column numbers for each column in the hosts table.
91 /// These are used for both retrieving data and for looking up
92 /// column labels for logging. Note that their numeric order
93 /// MUST match that of the column order in the hosts table.
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;
108 /// @brief Number of columns returned for SELECT queries sent by this class.
109 static const size_t HOST_COLUMNS = 14;
110
111 public:
112
113 /// @brief Constructor
114 ///
115 /// @param additional_columns_num This value is set by the derived classes
116 /// to indicate how many additional columns will be returned by SELECT
117 /// queries performed by the derived class. This constructor will allocate
118 /// resources for these columns, e.g. binding table, error indicators.
PgSqlHostExchange(const size_t additional_columns_num=0)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
143 /// @brief Virtual destructor.
~PgSqlHostExchange()144 virtual ~PgSqlHostExchange() {
145 }
146
147 /// @brief Reinitializes state information
148 ///
149 /// This function should be called in between statement executions.
150 /// Deriving classes should invoke this method as well as be reset
151 /// all of their own stateful values.
clear()152 virtual void clear() {
153 host_.reset();
154 };
155
156 /// @brief Returns index of the first uninitialized column name.
157 ///
158 /// This method is called by the derived classes to determine which
159 /// column indexes are available for the derived classes within a
160 /// binding array, error array and column names. This method
161 /// determines the first available index by searching the first
162 /// empty value within the columns_ vector. Previously we relied on
163 /// the fixed values set for each class, but this was hard to maintain
164 /// when new columns were added to the SELECT queries. It required
165 /// modifying indexes in all derived classes.
166 ///
167 /// Derived classes must call this method in their constructors and
168 /// use returned value as an index for the first column used by the
169 /// derived class and increment this value for each subsequent column.
findAvailColumn() const170 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
176 /// @brief Returns value of host id in the given row.
177 ///
178 /// This method is used to "look ahead" at the host_id in a row
179 /// without having to call retrieveHost()
getHostId(const PgSqlResult & r,int row)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
186 /// @brief Populate a bind array from a host
187 ///
188 /// Constructs a PsqlBindArray for sending data stored in a Host object
189 /// to the database.
190 ///
191 /// @param host Host object to be added to the database.
192 /// None of the fields in the host reservation are modified -
193 /// the host data is only read.
194 /// @param unique_ip boolean value indicating if multiple reservations for the
195 /// same IP address are allowed (false) or not (true).
196 ///
197 /// @return pointer to newly constructed bind_array containing the
198 /// bound values extracted from host
199 ///
200 /// @throw DbOperationError if bind_array cannot be populated.
createBindForSend(const HostPtr & host,const bool unique_ip)201 PsqlBindArrayPtr createBindForSend(const HostPtr& host, const bool unique_ip) {
202 if (!host) {
203 isc_throw(BadValue, "createBindForSend:: host object is NULL");
204 }
205
206 // Store the host to ensure bound values remain in scope
207 host_ = host;
208
209 // Bind the host data to the array
210 PsqlBindArrayPtr bind_array(new PsqlBindArray());
211 try {
212 // host_id : is auto_incremented skip it
213
214 // dhcp_identifier : BYTEA NOT NULL
215 bind_array->add(host->getIdentifier());
216
217 // dhcp_identifier_type : SMALLINT NOT NULL
218 bind_array->add(host->getIdentifierType());
219
220 // dhcp4_subnet_id : INT NULL
221 if (host->getIPv4SubnetID() == SUBNET_ID_UNUSED) {
222 bind_array->addNull();
223 }
224 else {
225 bind_array->add(host->getIPv4SubnetID());
226 }
227
228 // dhcp6_subnet_id : INT NULL
229 if (host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
230 bind_array->addNull();
231 }
232 else {
233 bind_array->add(host->getIPv6SubnetID());
234 }
235
236 // ipv4_address : BIGINT NULL
237 bind_array->add((host->getIPv4Reservation()));
238
239 // hostname : VARCHAR(255) NULL
240 bind_array->add(host->getHostname());
241
242 // dhcp4_client_classes : VARCHAR(255) NULL
243 // Override default separator to not include space after comma.
244 bind_array->addTempString(host->getClientClasses4().toText(","));
245
246 // dhcp6_client_classes : VARCHAR(255) NULL
247 bind_array->addTempString(host->getClientClasses6().toText(","));
248
249 // user_context: TEXT NULL
250 ConstElementPtr ctx = host->getContext();
251 if (ctx) {
252 std::string user_context_ = ctx->str();
253 bind_array->addTempString(user_context_);
254 } else {
255 bind_array->addNull();
256 }
257
258 // dhcp4_next_server : BIGINT NULL
259 bind_array->add((host->getNextServer()));
260
261 // dhcp4_server_hostname : VARCHAR(64)
262 bind_array->add(host->getServerHostname());
263
264 // dhcp4_boot_file_name : VARCHAR(128)
265 bind_array->add(host->getBootFileName());
266
267 // add auth keys
268 std::string key = host->getKey().toText();
269 if (key.empty()) {
270 bind_array->addNull();
271 } else {
272 bind_array->add(key);
273 }
274
275 // When checking whether the IP is unique we need to bind the IPv4 address
276 // at the end of the query as it has additional binding for the IPv4
277 // address.
278 if (unique_ip) {
279 bind_array->add(host->getIPv4Reservation()); // ipv4_address
280 bind_array->add(host->getIPv4SubnetID()); // subnet_id
281 }
282
283
284 } catch (const std::exception& ex) {
285 host_.reset();
286 isc_throw(DbOperationError,
287 "Could not create bind array from Host: "
288 << host->getHostname() << ", reason: " << ex.what());
289 }
290
291 return (bind_array);
292 };
293
294 /// @brief Processes one row of data fetched from a database.
295 ///
296 /// The processed data must contain host id, which uniquely identifies a
297 /// host. This method creates a host and inserts it to the hosts collection
298 /// only if the last inserted host has a different host id. This prevents
299 /// adding duplicated hosts to the collection, assuming that processed
300 /// rows are primarily ordered by host id column.
301 ///
302 /// This method must be overridden in the derived classes to also
303 /// retrieve IPv6 reservations and DHCP options associated with a host.
304 ///
305 /// @param [out] hosts Collection of hosts to which a new host created
306 /// from the processed data should be inserted.
processRowData(ConstHostCollection & hosts,const PgSqlResult & r,int row)307 virtual void processRowData(ConstHostCollection& hosts,
308 const PgSqlResult& r, int row) {
309 // Peek at the host id , so we can skip it if we already have it
310 // This lets us avoid constructing a copy of host for each
311 // of its sub-rows (options, etc...)
312 HostID row_host_id = getHostId(r, row);
313
314 // Add new host only if there are no hosts or the host id of the
315 // most recently added host is different than the host id of the
316 // currently processed host.
317 if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
318 HostPtr host = retrieveHost(r, row, row_host_id);
319 hosts.push_back(host);
320 }
321 }
322
323 /// @brief Creates a Host object from a given row in a result set.
324 ///
325 /// @param r result set containing one or more rows from the hosts table
326 /// @param row index within the result set of the row to process
327 /// @param peeked_host_id if the caller has peeked ahead at the row's
328 /// host_id, it can be passed in here to avoid fetching it from the row
329 /// a second time.
330 ///
331 /// @return HostPtr to the newly created Host object
332 ///
333 /// @throw DbOperationError if the host cannot be created.
retrieveHost(const PgSqlResult & r,int row,const HostID & peeked_host_id=0)334 HostPtr retrieveHost(const PgSqlResult& r, int row,
335 const HostID& peeked_host_id = 0) {
336
337 // If the caller peeked ahead at the host_id use that, otherwise
338 // read it from the row.
339 HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
340
341 // dhcp_identifier : BYTEA NOT NULL
342 uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
343 size_t identifier_len;
344 convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
345 sizeof(identifier_value), identifier_len);
346
347 // dhcp_identifier_type : SMALLINT NOT NULL
348 uint8_t type;
349 getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
350 if (type > MAX_IDENTIFIER_TYPE) {
351 isc_throw(BadValue, "invalid dhcp identifier type returned: "
352 << static_cast<int>(type));
353 }
354
355 Host::IdentifierType identifier_type =
356 static_cast<Host::IdentifierType>(type);
357
358 // dhcp4_subnet_id : INT NULL
359 uint32_t subnet_id(SUBNET_ID_UNUSED);
360 if (!isColumnNull(r, row, DHCP4_SUBNET_ID_COL)) {
361 getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
362 }
363 SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
364
365 // dhcp6_subnet_id : INT NULL
366 subnet_id = SUBNET_ID_UNUSED;
367 if (!isColumnNull(r, row, DHCP6_SUBNET_ID_COL)) {
368 getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
369 }
370 SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
371
372 // ipv4_address : BIGINT NULL
373 uint32_t addr4(0);
374 if (!isColumnNull(r, row, IPV4_ADDRESS_COL)) {
375 getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
376 }
377 isc::asiolink::IOAddress ipv4_reservation(addr4);
378
379 // hostname : VARCHAR(255) NULL
380 std::string hostname;
381 if (!isColumnNull(r, row, HOSTNAME_COL)) {
382 getColumnValue(r, row, HOSTNAME_COL, hostname);
383 }
384
385 // dhcp4_client_classes : VARCHAR(255) NULL
386 std::string dhcp4_client_classes;
387 if (!isColumnNull(r, row, DHCP4_CLIENT_CLASSES_COL)) {
388 getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
389 }
390
391 // dhcp6_client_classes : VARCHAR(255) NULL
392 std::string dhcp6_client_classes;
393 if (!isColumnNull(r, row, DHCP6_CLIENT_CLASSES_COL)) {
394 getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
395 }
396
397 // user_context: TEXT
398 std::string user_context;
399 if (!isColumnNull(r, row, USER_CONTEXT_COL)) {
400 getColumnValue(r, row, USER_CONTEXT_COL, user_context);
401 }
402
403 // dhcp4_next_server : BIGINT NULL
404 uint32_t dhcp4_next_server_as_uint32(0);
405 if (!isColumnNull(r, row, DHCP4_NEXT_SERVER_COL)) {
406 getColumnValue(r, row, DHCP4_NEXT_SERVER_COL, dhcp4_next_server_as_uint32);
407 }
408 isc::asiolink::IOAddress dhcp4_next_server(dhcp4_next_server_as_uint32);
409
410 // dhcp4_server_hostname : VARCHAR(64)
411 std::string dhcp4_server_hostname;
412 if (!isColumnNull(r, row, DHCP4_SERVER_HOSTNAME_COL)) {
413 getColumnValue(r, row, DHCP4_SERVER_HOSTNAME_COL, dhcp4_server_hostname);
414 }
415
416 // dhcp4_boot_file_name : VARCHAR(128)
417 std::string dhcp4_boot_file_name;
418 if (!isColumnNull(r, row, DHCP4_BOOT_FILE_NAME_COL)) {
419 getColumnValue(r, row, DHCP4_BOOT_FILE_NAME_COL, dhcp4_boot_file_name);
420 }
421
422 // auth_key : VARCHAR(16)
423 std::string auth_key;
424 if (!isColumnNull(r, row, AUTH_KEY_COL)) {
425 getColumnValue(r, row, AUTH_KEY_COL, auth_key);
426 }
427
428 // Finally, attempt to create the new host.
429 HostPtr host;
430 try {
431 host.reset(new Host(identifier_value, identifier_len,
432 identifier_type, dhcp4_subnet_id,
433 dhcp6_subnet_id, ipv4_reservation, hostname,
434 dhcp4_client_classes, dhcp6_client_classes,
435 dhcp4_next_server, dhcp4_server_hostname,
436 dhcp4_boot_file_name, AuthKey(auth_key)));
437
438 // Set the user context if there is one.
439 if (!user_context.empty()) {
440 try {
441 ConstElementPtr ctx = Element::fromJSON(user_context);
442 if (!ctx || (ctx->getType() != Element::map)) {
443 isc_throw(BadValue, "user context '" << user_context
444 << "' is not a JSON map");
445 }
446 host->setContext(ctx);
447 } catch (const isc::data::JSONError& ex) {
448 isc_throw(BadValue, "user context '" << user_context
449 << "' is invalid JSON: " << ex.what());
450 }
451 }
452
453 host->setHostId(host_id);
454 } catch (const isc::Exception& ex) {
455 isc_throw(DbOperationError, "Could not create host: " << ex.what());
456 }
457
458 return(host);
459 };
460
461 protected:
462 /// Pointer to Host object holding information to be inserted into
463 /// Hosts table. This is used to retain scope.
464 HostPtr host_;
465 };
466
467 /// @brief Extends base exchange class with ability to retrieve DHCP options
468 /// from the 'dhcp4_options' and 'dhcp6_options' tables.
469 ///
470 /// This class provides means to retrieve both DHCPv4 and DHCPv6 options
471 /// along with the host information. It is not used to retrieve IPv6
472 /// reservations. The following types of queries are supported:
473 /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
474 /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
475 /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
476 class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
477 private:
478
479 /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
480 static const size_t OPTION_COLUMNS = 7;
481
482 /// @brief Receives DHCPv4 or DHCPv6 options information from the
483 /// dhcp4_options or dhcp6_options tables respectively.
484 ///
485 /// The PgSqlHostWithOptionsExchange class holds two respective instances
486 /// of this class, one for receiving DHCPv4 options, one for receiving
487 /// DHCPv6 options.
488 ///
489 /// The following are the basic functions of this class:
490 /// - bind class members to specific columns in PgSQL binding tables,
491 /// - set DHCP options specific column names,
492 /// - create instances of options retrieved from the database.
493 ///
494 /// The reason for isolating those functions in a separate C++ class is
495 /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
496 class OptionProcessor {
497 public:
498
499 /// @brief Constructor.
500 ///
501 /// @param universe V4 or V6. The type of the options' instances
502 /// created by this class depends on this parameter.
503 /// @param start_column Index of the first column to be used by this
504 /// class.
OptionProcessor(const Option::Universe & universe,const size_t start_column)505 OptionProcessor(const Option::Universe& universe,
506 const size_t start_column)
507 : universe_(universe), start_column_(start_column),
508 option_id_index_(start_column), code_index_(start_column_ + 1),
509 value_index_(start_column_ + 2),
510 formatted_value_index_(start_column_ + 3),
511 space_index_(start_column_ + 4),
512 persistent_index_(start_column_ + 5),
513 user_context_index_(start_column_ + 6),
514 most_recent_option_id_(0) {
515 }
516
517 /// @brief Reinitializes state information
518 ///
519 /// This function should be called prior to processing a fetched
520 /// set of options.
clear()521 void clear() {
522 most_recent_option_id_ = 0;
523 }
524
525 /// @brief Creates instance of the currently processed option.
526 ///
527 /// This method detects if the currently processed option is a new
528 /// instance. It makes its determination by comparing the identifier
529 /// of the currently processed option, with the most recently processed
530 /// option. If the current value is greater than the id of the recently
531 /// processed option it is assumed that the processed row holds new
532 /// option information. In such case the option instance is created and
533 /// inserted into the configuration passed as argument.
534 ///
535 /// This logic is necessary to deal with result sets made from multiple
536 /// left joins which contain duplicated data. For instance queries
537 /// returning both v4 and v6 options for a host would generate result
538 /// sets similar to this:
539 /// @code
540 ///
541 /// row 0: host-1 v4-opt-1 v6-opt-1
542 /// row 1: host-1 v4-opt-1 v6-opt-2
543 /// row 2: host-1 v4-opt-1 v6-opt-3
544 /// row 4: host-1 v4-opt-2 v6-opt-1
545 /// row 5: host-1 v4-opt-2 v6-opt-2
546 /// row 6: host-1 v4-opt-2 v6-opt-3
547 /// row 7: host-2 v4-opt-1 v6-opt-1
548 /// row 8: host-2 v4-opt-2 v6-opt-1
549 /// :
550 /// @endcode
551 ///
552 /// @param cfg Pointer to the configuration object into which new
553 /// option instances should be inserted.
554 /// @param r result set containing one or more rows from a dhcp
555 /// options table.
556 /// @param row index within the result set of the row to process
retrieveOption(const CfgOptionPtr & cfg,const PgSqlResult & r,int row)557 void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
558 int row) {
559 // If the option id on this row is NULL, then there's no
560 // option of this type (4/6) on this row to fetch, so bail.
561 if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
562 return;
563 }
564
565 // option_id: INT
566 uint64_t option_id;
567 PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);
568
569 // The row option id must be greater than id if the most recent
570 // option because they are ordered by option id. Otherwise
571 // we assume that we have already processed this option.
572 if (most_recent_option_id_ >= option_id) {
573 return;
574 }
575
576 // Remember current option id as the most recent processed one. We
577 // will be comparing it with option ids in subsequent rows.
578 most_recent_option_id_ = option_id;
579
580 // code: SMALLINT NOT NULL
581 uint16_t code;
582 PgSqlExchange::getColumnValue(r, row, code_index_, code);
583
584 // value: BYTEA
585 uint8_t value[OPTION_VALUE_MAX_LEN];
586 size_t value_len(0);
587 if (!isColumnNull(r, row, value_index_)) {
588 PgSqlExchange::convertFromBytea(r, row, value_index_, value,
589 sizeof(value), value_len);
590 }
591
592 // formatted_value: TEXT
593 std::string formatted_value;
594 if (!isColumnNull(r, row, formatted_value_index_)) {
595 PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
596 formatted_value);
597 }
598
599 // space: VARCHAR(128)
600 std::string space;
601 if (!isColumnNull(r, row, space_index_)) {
602 PgSqlExchange::getColumnValue(r, row, space_index_, space);
603 }
604
605 // If empty or null space provided, use a default top level space.
606 if (space.empty()) {
607 space = (universe_ == Option::V4 ?
608 DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
609 }
610
611 // persistent: BOOL default false
612 bool persistent;
613 PgSqlExchange::getColumnValue(r, row, persistent_index_,
614 persistent);
615
616 // user_context: TEXT
617 std::string user_context;
618 if (!isColumnNull(r, row, user_context_index_)) {
619 PgSqlExchange::getColumnValue(r, row, user_context_index_,
620 user_context);
621 }
622
623 // Options are held in a binary or textual format in the database.
624 // This is similar to having an option specified in a server
625 // configuration file. Such option is converted to appropriate C++
626 // class, using option definition. Thus, we need to find the
627 // option definition for this option code and option space.
628
629 // If the option space is a standard DHCPv4 or DHCPv6 option space,
630 // this is most likely a standard option, for which we have a
631 // definition created within libdhcp++.
632 OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
633
634 // Otherwise, we may check if this an option encapsulated within the
635 // vendor space.
636 if (!def && (space != DHCP4_OPTION_SPACE) &&
637 (space != DHCP6_OPTION_SPACE)) {
638 uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
639 if (vendor_id > 0) {
640 def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
641 code);
642 }
643 }
644
645 // In all other cases, we use runtime option definitions, which
646 // should be also registered within the libdhcp++.
647 if (!def) {
648 def = LibDHCP::getRuntimeOptionDef(space, code);
649 }
650
651 OptionPtr option;
652
653 if (!def) {
654 // If no definition found, we use generic option type.
655 OptionBuffer buf(value, value + value_len);
656 option.reset(new Option(universe_, code, buf.begin(),
657 buf.end()));
658 } else {
659 // The option value may be specified in textual or binary format
660 // in the database. If formatted_value is empty, the binary
661 // format is used. Depending on the format we use a different
662 // variant of the optionFactory function.
663 if (formatted_value.empty()) {
664 OptionBuffer buf(value, value + value_len);
665 option = def->optionFactory(universe_, code, buf.begin(),
666 buf.end());
667 } else {
668 // Spit the value specified in comma separated values
669 // format.
670 std::vector<std::string> split_vec;
671 boost::split(split_vec, formatted_value,
672 boost::is_any_of(","));
673 option = def->optionFactory(universe_, code, split_vec);
674 }
675 }
676
677 OptionDescriptor desc(option, persistent, formatted_value);
678
679 // Set the user context if there is one into the option descriptor.
680 if (!user_context.empty()) {
681 try {
682 ConstElementPtr ctx = Element::fromJSON(user_context);
683 if (!ctx || (ctx->getType() != Element::map)) {
684 isc_throw(BadValue, "user context '" << user_context
685 << "' is no a JSON map");
686 }
687 desc.setContext(ctx);
688 } catch (const isc::data::JSONError& ex) {
689 isc_throw(BadValue, "user context '" << user_context
690 << "' is invalid JSON: " << ex.what());
691 }
692 }
693
694 cfg->add(desc, space);
695 }
696
697 /// @brief Specify column names.
698 ///
699 /// @param [out] columns Reference to a vector holding names of option
700 /// specific columns.
setColumnNames(std::vector<std::string> & columns)701 void setColumnNames(std::vector<std::string>& columns) {
702 columns[option_id_index_] = "option_id";
703 columns[code_index_] = "code";
704 columns[value_index_] = "value";
705 columns[formatted_value_index_] = "formatted_value";
706 columns[space_index_] = "space";
707 columns[persistent_index_] = "persistent";
708 columns[user_context_index_] = "user_context";
709 }
710
711 private:
712 /// @brief Universe: V4 or V6.
713 Option::Universe universe_;
714
715 /// @brief Index of first column used by this class.
716 size_t start_column_;
717
718 //@}
719
720 /// @name Indexes of the specific columns
721 //@{
722 /// @brief Option id
723 size_t option_id_index_;
724
725 /// @brief Code
726 size_t code_index_;
727
728 /// @brief Value
729 size_t value_index_;
730
731 /// @brief Formatted value
732 size_t formatted_value_index_;
733
734 /// @brief Space
735 size_t space_index_;
736
737 /// @brief Persistent
738 size_t persistent_index_;
739 //@}
740
741 /// @brief User context
742 size_t user_context_index_;
743
744 /// @brief Option id for last processed row.
745 uint64_t most_recent_option_id_;
746 };
747
748 /// @brief Pointer to the @ref OptionProcessor class.
749 typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
750
751 public:
752
753 /// @brief DHCP option types to be fetched from the database.
754 ///
755 /// Supported types are:
756 /// - Only DHCPv4 options,
757 /// - Only DHCPv6 options,
758 /// - Both DHCPv4 and DHCPv6 options.
759 enum FetchedOptions {
760 DHCP4_ONLY,
761 DHCP6_ONLY,
762 DHCP4_AND_DHCP6
763 };
764
765 /// @brief Constructor.
766 ///
767 /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
768 /// be fetched from the database for a host.
769 /// @param additional_columns_num Number of additional columns for which
770 /// resources should be allocated, e.g. binding table, column names etc.
771 /// This parameter should be set to a non zero value by derived classes to
772 /// allocate resources for the columns supported by derived classes.
PgSqlHostWithOptionsExchange(const FetchedOptions & fetched_options,const size_t additional_columns_num=0)773 PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
774 const size_t additional_columns_num = 0)
775 : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
776 + additional_columns_num),
777 opt_proc4_(), opt_proc6_() {
778
779 // Create option processor for DHCPv4 options, if required.
780 if ((fetched_options == DHCP4_ONLY) ||
781 (fetched_options == DHCP4_AND_DHCP6)) {
782 opt_proc4_.reset(new OptionProcessor(Option::V4,
783 findAvailColumn()));
784 opt_proc4_->setColumnNames(columns_);
785 }
786
787 // Create option processor for DHCPv6 options, if required.
788 if ((fetched_options == DHCP6_ONLY) ||
789 (fetched_options == DHCP4_AND_DHCP6)) {
790 opt_proc6_.reset(new OptionProcessor(Option::V6,
791 findAvailColumn()));
792 opt_proc6_->setColumnNames(columns_);
793 }
794 }
795
796 /// @brief Clears state information
797 ///
798 /// This function should be called in between statement executions.
799 /// Deriving classes should invoke this method as well as be reset
800 /// all of their own stateful values.
clear()801 virtual void clear() {
802 PgSqlHostExchange::clear();
803 if (opt_proc4_) {
804 opt_proc4_->clear();
805 }
806
807 if (opt_proc6_) {
808 opt_proc6_->clear();
809 }
810 }
811
812 /// @brief Processes the current row.
813 ///
814 /// The fetched row includes both host information and DHCP option
815 /// information. Because the SELECT queries use one or more LEFT JOIN
816 /// clauses, the result set may contain duplicated host or options
817 /// entries. This method detects duplicated information and discards such
818 /// entries.
819 ///
820 /// @param [out] hosts Container holding parsed hosts and options.
processRowData(ConstHostCollection & hosts,const PgSqlResult & r,int row)821 virtual void processRowData(ConstHostCollection& hosts,
822 const PgSqlResult& r, int row) {
823 HostPtr current_host;
824 if (hosts.empty()) {
825 // Must be the first one, fetch it.
826 current_host = retrieveHost(r, row);
827 hosts.push_back(current_host);
828 } else {
829 // Peek at the host id so we can skip it if we already have
830 // this host. This lets us avoid retrieving the host needlessly
831 // for each of its sub-rows (options, etc...).
832 HostID row_host_id = getHostId(r, row);
833 current_host = boost::const_pointer_cast<Host>(hosts.back());
834
835 // if the row's host id is greater than the one we've been
836 // working on we're starting a new host, so fetch it.
837 if (row_host_id > current_host->getHostId()) {
838 current_host = retrieveHost(r, row, row_host_id);
839 hosts.push_back(current_host);
840 }
841 }
842
843 // Parse DHCPv4 options if required to do so.
844 if (opt_proc4_) {
845 CfgOptionPtr cfg = current_host->getCfgOption4();
846 opt_proc4_->retrieveOption(cfg, r, row);
847 }
848
849 // Parse DHCPv6 options if required to do so.
850 if (opt_proc6_) {
851 CfgOptionPtr cfg = current_host->getCfgOption6();
852 opt_proc6_->retrieveOption(cfg, r, row);
853 }
854 }
855
856 private:
857
858 /// @brief Returns a number of columns required to retrieve option data.
859 ///
860 /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
861 /// DHCPv6 a different number of columns is required in the binding array.
862 /// This method returns the number of required columns, according to the
863 /// value of @c fetched_columns passed in the constructor.
864 ///
865 /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
866 /// both types of options should be retrieved.
867 ///
868 /// @return Number of required columns.
getRequiredColumnsNum(const FetchedOptions & fetched_options)869 static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
870 return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
871 OPTION_COLUMNS);
872 }
873
874 /// @brief Pointer to DHCPv4 options processor.
875 ///
876 /// If this object is NULL, the DHCPv4 options are not fetched.
877 OptionProcessorPtr opt_proc4_;
878
879 /// @brief Pointer to DHCPv6 options processor.
880 ///
881 /// If this object is NULL, the DHCPv6 options are not fetched.
882 OptionProcessorPtr opt_proc6_;
883 };
884
885 /// @brief This class provides mechanisms for sending and retrieving
886 /// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
887 ///
888 /// This class extends the @ref PgSqlHostWithOptionsExchange class with the
889 /// mechanisms to retrieve IPv6 reservations. This class is used in situations
890 /// when it is desired to retrieve DHCPv6 specific information about the host
891 /// (DHCPv6 options and reservations), or entire information about the host
892 /// (DHCPv4 options, DHCPv6 options and reservations). The following are the
893 /// queries used with this class:
894 /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
895 /// LEFT JOIN ipv6_reservations ...
896 /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
897 class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
898 private:
899
900 /// @brief Number of columns holding IPv6 reservation information.
901 static const size_t RESERVATION_COLUMNS = 5;
902
903 public:
904
905 /// @brief Constructor.
906 ///
907 /// Apart from initializing the base class data structures it also
908 /// initializes values representing IPv6 reservation information.
PgSqlHostIPv6Exchange(const FetchedOptions & fetched_options)909 PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
910 : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
911 reservation_id_index_(findAvailColumn()),
912 address_index_(reservation_id_index_ + 1),
913 prefix_len_index_(reservation_id_index_ + 2),
914 type_index_(reservation_id_index_ + 3),
915 iaid_index_(reservation_id_index_ + 4),
916 most_recent_reservation_id_(0) {
917
918 // Provide names of additional columns returned by the queries.
919 columns_[reservation_id_index_] = "reservation_id";
920 columns_[address_index_] = "address";
921 columns_[prefix_len_index_] = "prefix_len";
922 columns_[type_index_] = "type";
923 columns_[iaid_index_] = "dhcp6_iaid";
924
925 BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
926 }
927
928 /// @brief Reinitializes state information
929 ///
930 /// This function should be called in between statement executions.
931 /// Deriving classes should invoke this method as well as be reset
932 /// all of their own stateful values.
clear()933 void clear() {
934 PgSqlHostWithOptionsExchange::clear();
935 most_recent_reservation_id_ = 0;
936 }
937
938 /// @brief Returns reservation id from the row.
939 ///
940 /// @return Reservation id or 0 if no reservation data is fetched.
getReservationId(const PgSqlResult & r,int row) const941 uint64_t getReservationId(const PgSqlResult& r, int row) const {
942 uint64_t resv_id = 0;
943 if (!isColumnNull(r, row, reservation_id_index_)) {
944 getColumnValue(r, row, reservation_id_index_, resv_id);
945 }
946
947 return (resv_id);
948 };
949
950 /// @brief Creates IPv6 reservation from the data contained in the
951 /// currently processed row.
952 ///
953 /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
retrieveReservation(const PgSqlResult & r,int row)954 IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
955
956 // type: SMALLINT NOT NULL
957 uint16_t tmp;
958 getColumnValue(r, row, type_index_, tmp);
959
960 // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
961 IPv6Resrv::Type resv_type;
962 switch (tmp) {
963 case 0:
964 resv_type = IPv6Resrv::TYPE_NA;
965 break;
966
967 case 2:
968 resv_type = IPv6Resrv::TYPE_PD;
969 break;
970
971 default:
972 isc_throw(BadValue,
973 "invalid IPv6 reservation type returned: "
974 << tmp << ". Only 0 or 2 are allowed.");
975 }
976
977 // address VARCHAR(39) NOT NULL
978 isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));
979
980 // prefix_len: SMALLINT NOT NULL
981 uint16_t prefix_len;
982 getColumnValue(r, row, prefix_len_index_, prefix_len);
983
984 // @todo once we support populating iaid
985 // iaid: INT
986 // int iaid;
987 // getColumnValue(r, row, iaid_index_, iaid);
988
989 // Create the reservation.
990 IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
991 return (reservation);
992 };
993
994 /// @brief Processes one row of data fetched from a database.
995 ///
996 /// The processed data must contain host id, which uniquely identifies a
997 /// host. This method creates a host and inserts it to the hosts collection
998 /// only if the last inserted host has a different host id. This prevents
999 /// adding duplicated hosts to the collection, assuming that processed
1000 /// rows are primarily ordered by host id column.
1001 ///
1002 /// Depending on the value of the @c fetched_options specified in the
1003 /// constructor, this method also parses options returned as a result
1004 /// of SELECT queries.
1005 ///
1006 /// For any returned row which contains IPv6 reservation information it
1007 /// checks if the reservation is not a duplicate of previously parsed
1008 /// reservation and appends the IPv6Resrv object into the host object
1009 /// if the parsed row contains new reservation information.
1010 ///
1011 /// @param [out] hosts Collection of hosts to which a new host created
1012 /// from the processed data should be inserted.
1013 /// @param r result set containing one or more rows of fetched data
1014 /// @param row index within the result set of the row to process
processRowData(ConstHostCollection & hosts,const PgSqlResult & r,int row)1015 virtual void processRowData(ConstHostCollection& hosts,
1016 const PgSqlResult& r, int row) {
1017 // Call parent class to fetch host information and options.
1018 PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
1019
1020 // Shouldn't happen but just in case
1021 if (hosts.empty()) {
1022 isc_throw(Unexpected, "no host information while retrieving"
1023 " IPv6 reservation");
1024 }
1025
1026 // If we have reservation id we haven't seen yet, retrieve the
1027 // the reservation, adding it to the current host
1028 uint64_t reservation_id = getReservationId(r, row);
1029 if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
1030 HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
1031 host->addReservation(retrieveReservation(r, row));
1032 most_recent_reservation_id_ = reservation_id;
1033 }
1034 }
1035
1036 private:
1037 /// @name Indexes of columns holding information about IPv6 reservations.
1038 //@{
1039 /// @brief Index of reservation_id column.
1040 size_t reservation_id_index_;
1041
1042 /// @brief Index of address column.
1043 size_t address_index_;
1044
1045 /// @brief Index of prefix_len column.
1046 size_t prefix_len_index_;
1047
1048 /// @brief Index of type column.
1049 size_t type_index_;
1050
1051 /// @brief Index of IAID column.
1052 size_t iaid_index_;
1053
1054 //@}
1055
1056 /// @brief Reservation id for last processed row.
1057 uint64_t most_recent_reservation_id_;
1058 };
1059
1060 /// @brief This class is used for storing IPv6 reservations in a PgSQL database.
1061 ///
1062 /// This class is only used to insert IPv6 reservations into the
1063 /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
1064 /// retrieve IPv6 reservation the @ref PgSqlIPv6HostExchange class should be
1065 /// used instead.
1066 ///
1067 /// When a new IPv6 reservation is inserted into the database, an appropriate
1068 /// host must be defined in the hosts table. An attempt to insert IPv6
1069 /// reservation for non-existing host will result in failure.
1070 class PgSqlIPv6ReservationExchange : public PgSqlExchange {
1071 private:
1072
1073 /// @brief Set number of columns for ipv6_reservation table.
1074 static const size_t RESRV_COLUMNS = 6;
1075
1076 public:
1077
1078 /// @brief Constructor
1079 ///
1080 /// Initialize class members representing a single IPv6 reservation.
PgSqlIPv6ReservationExchange()1081 PgSqlIPv6ReservationExchange()
1082 : PgSqlExchange(RESRV_COLUMNS),
1083 resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
1084 // Set the column names (for error messages)
1085 columns_[0] = "host_id";
1086 columns_[1] = "address";
1087 columns_[2] = "prefix_len";
1088 columns_[3] = "type";
1089 columns_[4] = "dhcp6_iaid";
1090 BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
1091 }
1092
1093 /// @brief Populate a bind array representing an IPv6 reservation
1094 ///
1095 /// Constructs a PsqlBindArray for an IPv6 reservation to the database.
1096 ///
1097 /// @param resv The IPv6 reservation to be added to the database.
1098 /// None of the fields in the reservation are modified -
1099 /// @param host_id ID of the host to which this reservation belongs.
1100 /// @param unique_ip boolean value indicating if multiple reservations for the
1101 /// same IP address are allowed (false) or not (true).
1102 ///
1103 /// @return pointer to newly constructed bind_array containing the
1104 /// bound values extracted the IPv6 reservation
1105 ///
1106 /// @throw DbOperationError if bind_array cannot be populated.
createBindForSend(const IPv6Resrv & resv,const HostID & host_id,const bool unique_ip)1107 PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
1108 const HostID& host_id,
1109 const bool unique_ip) {
1110 // Store the values to ensure they remain valid.
1111 // Technically we don't need this, as currently all the values
1112 // are converted to strings and stored by the bind array.
1113 resv_ = resv;
1114
1115 PsqlBindArrayPtr bind_array(new PsqlBindArray());
1116
1117 try {
1118 // address VARCHAR(39) NOT NULL
1119 bind_array->add(resv.getPrefix());
1120
1121 // prefix_len: SMALLINT NOT NULL
1122 bind_array->add(resv.getPrefixLen());
1123
1124 // type: SMALLINT NOT NULL
1125 // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
1126 uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
1127 bind_array->add(type);
1128
1129 // dhcp6_iaid: INT UNSIGNED
1130 /// @todo: We don't support iaid in the IPv6Resrv yet.
1131 bind_array->addNull();
1132
1133 // host_id: BIGINT NOT NULL
1134 bind_array->add(host_id);
1135
1136 // When checking whether the IP is unique we need to bind the IPv6 address
1137 // and prefix length at the end of the query as it has additional binding
1138 // for the IPv6 address and prefix length.
1139 if (unique_ip) {
1140 bind_array->add(resv.getPrefix()); // address
1141 bind_array->add(resv.getPrefixLen()); // prefix_len
1142 }
1143 } catch (const std::exception& ex) {
1144 isc_throw(DbOperationError,
1145 "Could not create bind array from IPv6 Reservation: "
1146 << resv_.toText() << ", reason: " << ex.what());
1147 }
1148
1149 return (bind_array);
1150 }
1151
1152 private:
1153 /// @brief Object holding reservation being sent to the database.
1154 IPv6Resrv resv_;
1155 };
1156
1157 /// @brief This class is used for inserting options into a database.
1158 ///
1159 /// This class supports inserting both DHCPv4 and DHCPv6 options.
1160 class PgSqlOptionExchange : public PgSqlExchange {
1161 private:
1162
1163 static const int OPTION_ID_COL = 0;
1164 static const int CODE_COL = 1;
1165 static const int VALUE_COL = 2;
1166 static const int FORMATTED_VALUE_COL = 3;
1167 static const int SPACE_COL = 4;
1168 static const int PERSISTENT_COL = 5;
1169 static const int USER_CONTEXT_COL = 6;
1170 static const int DHCP_CLIENT_CLASS_COL = 7;
1171 static const int DHCP_SUBNET_ID_COL = 8;
1172 static const int HOST_ID_COL = 9;
1173 static const int SCOPE_ID_COL = 10;
1174
1175 /// @brief Number of columns in the tables holding options.
1176 static const size_t OPTION_COLUMNS = 11;
1177
1178 public:
1179
1180 /// @brief Constructor.
PgSqlOptionExchange()1181 PgSqlOptionExchange()
1182 : PgSqlExchange(OPTION_COLUMNS), value_(),
1183 value_len_(0), option_() {
1184 columns_[OPTION_ID_COL] = "option_id";
1185 columns_[CODE_COL] = "code";
1186 columns_[VALUE_COL] = "value";
1187 columns_[FORMATTED_VALUE_COL] = "formatted_value";
1188 columns_[SPACE_COL] = "space";
1189 columns_[PERSISTENT_COL] = "persistent";
1190 columns_[USER_CONTEXT_COL] = "user_context";
1191 columns_[DHCP_CLIENT_CLASS_COL] = "dhcp_client_class";
1192 columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
1193 columns_[HOST_ID_COL] = "host_id";
1194 columns_[SCOPE_ID_COL] = "scope_id";
1195
1196 BOOST_STATIC_ASSERT(10 < OPTION_COLUMNS);
1197 }
1198
1199 /// @brief Creates binding array to insert option data into database.
1200 ///
1201 /// @param opt_desc option descriptor of the option to write
1202 /// @param opt_space name of the option space to which the option belongs
1203 /// @param host_id host id of the host to which the option belongs
1204 ///
1205 /// @return pointer to newly constructed bind_array containing the
1206 /// bound values extracted from host
createBindForSend(const OptionDescriptor & opt_desc,const std::string & opt_space,const HostID & host_id)1207 PsqlBindArrayPtr createBindForSend(const OptionDescriptor& opt_desc,
1208 const std::string& opt_space,
1209 const HostID& host_id) {
1210 // Hold pointer to the option to make sure it remains valid until
1211 // we complete a query.
1212 option_ = opt_desc.option_;
1213
1214 // Create the bind-array
1215 PsqlBindArrayPtr bind_array(new PsqlBindArray());
1216
1217 try {
1218 // option_id: is auto_incremented so skip it
1219
1220 // code: SMALLINT UNSIGNED NOT NULL
1221 bind_array->add(option_->getType());
1222
1223 // value: BYTEA NULL
1224 if (opt_desc.formatted_value_.empty() &&
1225 (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
1226 // The formatted_value is empty and the option value is
1227 // non-empty so we need to prepare on-wire format for the
1228 // option and store it in the database as a BYTEA.
1229 OutputBuffer buf(opt_desc.option_->len());
1230 opt_desc.option_->pack(buf);
1231 const char* buf_ptr = static_cast<const char*>(buf.getData());
1232 value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
1233 buf_ptr + buf.getLength());
1234 value_len_ = value_.size();
1235 bind_array->add(value_);
1236 } else {
1237 // No value or formatted_value specified. In this case, the
1238 // value BYTEA should be NULL.
1239 bind_array->addNull(PsqlBindArray::BINARY_FMT);
1240 }
1241
1242 // formatted_value: TEXT NULL,
1243 if (!opt_desc.formatted_value_.empty()) {
1244 bind_array->addTempString(opt_desc.formatted_value_);
1245 } else {
1246 bind_array->addNull();
1247 }
1248
1249 // space: VARCHAR(128) NULL
1250 if (!opt_space.empty()) {
1251 bind_array->addTempString(opt_space);
1252 } else {
1253 bind_array->addNull();
1254 }
1255
1256 // persistent: BOOLEAN DEFAULT false
1257 bind_array->add(opt_desc.persistent_);
1258
1259 // user_context: TEXT NULL,
1260 ConstElementPtr ctx = opt_desc.getContext();
1261 if (ctx) {
1262 std::string user_context_ = ctx->str();
1263 bind_array->addTempString(user_context_);
1264 } else {
1265 bind_array->addNull();
1266 }
1267
1268 // host_id: INT NULL
1269 if (!host_id) {
1270 isc_throw(BadValue, "host_id cannot be null");
1271 }
1272 bind_array->add(host_id);
1273
1274 } catch (const std::exception& ex) {
1275 isc_throw(DbOperationError,
1276 "Could not create bind array for inserting DHCP "
1277 "host option: " << option_->toText() << ", reason: "
1278 << ex.what());
1279 }
1280
1281 return (bind_array);
1282 }
1283
1284 private:
1285
1286 /// @brief Option value as binary.
1287 std::vector<uint8_t> value_;
1288
1289 /// @brief Option value length.
1290 size_t value_len_;
1291
1292 /// @brief Pointer to currently parsed option.
1293 OptionPtr option_;
1294 };
1295
1296 } // namespace
1297
1298 namespace isc {
1299 namespace dhcp {
1300
1301 /// @brief PostgreSQL Host Context
1302 ///
1303 /// This class stores the thread context for the manager pool.
1304 /// The class is needed by all get/update/delete functions which must use one
1305 /// or more exchanges to perform database operations.
1306 /// Each context provides a set of such exchanges for each thread.
1307 /// The context instances are lazy initialized by the requesting thread by using
1308 /// the manager's createContext function and are destroyed when the manager's
1309 /// pool instance is destroyed.
1310 class PgSqlHostContext {
1311 public:
1312
1313 /// @brief Constructor
1314 ///
1315 /// @param parameters See PgSqlHostMgr constructor.
1316 /// @param io_service_accessor The IOService accessor function.
1317 /// @param db_reconnect_callback The connection recovery callback.
1318 PgSqlHostContext(const DatabaseConnection::ParameterMap& parameters,
1319 IOServiceAccessorPtr io_service_accessor,
1320 db::DbCallback db_reconnect_callback);
1321
1322 /// The exchange objects are used for transfer of data to/from the database.
1323 /// They are pointed-to objects as the contents may change in "const" calls,
1324 /// while the rest of this object does not. (At alternative would be to
1325 /// declare them as "mutable".)
1326
1327 /// @brief Pointer to the object representing an exchange which
1328 /// can be used to retrieve hosts and DHCPv4 options.
1329 boost::shared_ptr<PgSqlHostWithOptionsExchange> host_ipv4_exchange_;
1330
1331 /// @brief Pointer to an object representing an exchange which can
1332 /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
1333 boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
1334
1335 /// @brief Pointer to an object representing an exchange which can
1336 /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
1337 /// IPv6 reservations using a single query.
1338 boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
1339
1340 /// @brief Pointer to an object representing an exchange which can
1341 /// be used to insert new IPv6 reservation.
1342 boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
1343
1344 /// @brief Pointer to an object representing an exchange which can
1345 /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
1346 /// or dhcp6_options table.
1347 boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
1348
1349 /// @brief PostgreSQL connection
1350 PgSqlConnection conn_;
1351
1352 /// @brief Indicates if the database is opened in read only mode.
1353 bool is_readonly_;
1354 };
1355
1356 /// @brief PostgreSQL Host Context Pool
1357 ///
1358 /// This class provides a pool of contexts.
1359 /// The manager will use this class to handle available contexts.
1360 /// There is only one ContextPool per manager per back-end, which is created
1361 /// and destroyed by the respective manager factory class.
1362 class PgSqlHostContextPool {
1363 public:
1364
1365 /// @brief The vector of available contexts.
1366 std::vector<PgSqlHostContextPtr> pool_;
1367
1368 /// @brief The mutex to protect pool access.
1369 std::mutex mutex_;
1370 };
1371
1372 /// @brief Type of pointers to context pools.
1373 typedef boost::shared_ptr<PgSqlHostContextPool> PgSqlHostContextPoolPtr;
1374
1375 /// @brief Implementation of the @ref PgSqlHostDataSource.
1376 class PgSqlHostDataSourceImpl {
1377 public:
1378
1379 /// @brief Statement Tags
1380 ///
1381 /// The contents of the enum are indexes into the list of SQL statements.
1382 /// It is assumed that the order is such that the indices of statements
1383 /// reading the database are less than those of statements modifying the
1384 /// database.
1385 /// @note: please add new statements doing read only operations before
1386 /// the WRITE_STMTS_BEGIN position.
1387 enum StatementIndex {
1388 GET_HOST_DHCPID, // Gets hosts by host identifier
1389 GET_HOST_ADDR, // Gets hosts by IPv4 address
1390 GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
1391 GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
1392 GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
1393 GET_HOST_PREFIX, // Gets host by IPv6 prefix
1394 GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
1395 GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
1396 GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
1397 GET_HOST_HOSTNAME, // Gets hosts by hostname
1398 GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
1399 GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
1400 GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
1401 GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
1402 GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
1403 GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
1404 INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
1405 INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
1406 INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
1407 INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
1408 INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
1409 INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
1410 DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
1411 DEL_HOST_ADDR6, // Delete v6 host (subnet-id, addr6)
1412 DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
1413 DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
1414 NUM_STATEMENTS // Number of statements
1415 };
1416
1417 /// @brief Index of first statement performing write to the database.
1418 ///
1419 /// This value is used to mark border line between queries and other
1420 /// statements and statements performing write operation on the database,
1421 /// such as INSERT, DELETE, UPDATE.
1422 static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_NON_UNIQUE_IP;
1423
1424 /// @brief Constructor.
1425 ///
1426 /// This constructor opens database connection and initializes prepared
1427 /// statements used in the queries.
1428 PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters);
1429
1430 /// @brief Destructor.
1431 ~PgSqlHostDataSourceImpl();
1432
1433 /// @brief Attempts to reconnect the server to the host DB backend manager.
1434 ///
1435 /// This is a self-rescheduling function that attempts to reconnect to the
1436 /// server's host DB backends after connectivity to one or more have been
1437 /// lost. Upon entry it will attempt to reconnect via
1438 /// @ref HostDataSourceFactory::add.
1439 /// If this is successful, DHCP servicing is re-enabled and server returns
1440 /// to normal operation.
1441 ///
1442 /// If reconnection fails and the maximum number of retries has not been
1443 /// exhausted, it will schedule a call to itself to occur at the
1444 /// configured retry interval. DHCP service remains disabled.
1445 ///
1446 /// If the maximum number of retries has been exhausted an error is logged
1447 /// and the server shuts down.
1448 ///
1449 /// This function is passed to the connection recovery mechanism. It will be
1450 /// invoked when a connection loss is detected.
1451 ///
1452 /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
1453 /// configured reconnect parameters.
1454 /// @return true if connection has been recovered, false otherwise.
1455 static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
1456
1457 /// @brief Create a new context.
1458 ///
1459 /// The database is opened with all the SQL commands pre-compiled.
1460 ///
1461 /// @return A new (never null) context.
1462 ///
1463 /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
1464 /// @throw isc::db::DbOperationError An operation on the open database has
1465 /// failed.
1466 PgSqlHostContextPtr createContext() const;
1467
1468 /// @brief Executes statements which insert a row into one of the tables.
1469 ///
1470 /// @param ctx Context
1471 /// @param stindex Index of a statement being executed.
1472 /// @param bind Vector of PgsqlBindArray objects to be used for the query
1473 /// @param return_last_id flag indicating whether or not the insert
1474 /// returns the primary key of from the row inserted via " RETURNING
1475 /// <primary key> as pid" clause on the INSERT statement. The RETURNING
1476 /// clause causes the INSERT to return a result set that should consist
1477 /// of a single row with one column, the value of the primary key.
1478 /// Defaults to false.
1479 ///
1480 /// @return 0 if return_last_id is false, otherwise it returns the
1481 /// the value in the result set in the first col of the first row.
1482 ///
1483 /// @throw isc::db::DuplicateEntry Database throws duplicate entry error
1484 uint64_t addStatement(PgSqlHostContextPtr& ctx,
1485 PgSqlHostDataSourceImpl::StatementIndex stindex,
1486 PsqlBindArrayPtr& bind,
1487 const bool return_last_id = false);
1488
1489 /// @brief Executes statements that delete records.
1490 ///
1491 /// @param ctx Context
1492 /// @param stindex Index of a statement being executed.
1493 /// @param bind pointer to PsqlBindArray objects to be used for the query
1494 ///
1495 /// @return true if any records were deleted, false otherwise
1496 bool delStatement(PgSqlHostContextPtr& ctx,
1497 PgSqlHostDataSourceImpl::StatementIndex stindex,
1498 PsqlBindArrayPtr& bind);
1499
1500 /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
1501 ///
1502 /// @param ctx Context
1503 /// @param resv IPv6 Reservation to be added
1504 /// @param id ID of a host owning this reservation
1505 void addResv(PgSqlHostContextPtr& ctx,
1506 const IPv6Resrv& resv,
1507 const HostID& id);
1508
1509 /// @brief Inserts a single DHCP option into the database.
1510 ///
1511 /// @param ctx Context
1512 /// @param stindex Index of a statement being executed.
1513 /// @param opt_desc Option descriptor holding information about an option
1514 /// to be inserted into the database.
1515 /// @param opt_space Option space name.
1516 /// @param subnet_id Subnet identifier.
1517 /// @param host_id Host identifier.
1518 void addOption(PgSqlHostContextPtr& ctx,
1519 const PgSqlHostDataSourceImpl::StatementIndex& stindex,
1520 const OptionDescriptor& opt_desc,
1521 const std::string& opt_space,
1522 const Optional<SubnetID>& subnet_id,
1523 const HostID& host_id);
1524
1525 /// @brief Inserts multiple options into the database.
1526 ///
1527 /// @param ctx Context
1528 /// @param stindex Index of a statement being executed.
1529 /// @param options_cfg An object holding a collection of options to be
1530 /// inserted into the database.
1531 /// @param host_id Host identifier retrieved using getColumnValue
1532 /// in addStatement method
1533 void addOptions(PgSqlHostContextPtr& ctx,
1534 const StatementIndex& stindex,
1535 const ConstCfgOptionPtr& options_cfg,
1536 const uint64_t host_id);
1537
1538 /// @brief Creates collection of @ref Host objects with associated
1539 /// information such as IPv6 reservations and/or DHCP options.
1540 ///
1541 /// This method performs a query which returns host information from
1542 /// the 'hosts' table. The query may also use LEFT JOIN clause to
1543 /// retrieve information from other tables, e.g. ipv6_reservations,
1544 /// dhcp4_options and dhcp6_options.
1545 /// Whether IPv6 reservations and/or options are assigned to the
1546 /// @ref Host objects depends on the type of the exchange object.
1547 ///
1548 /// @param ctx Context
1549 /// @param stindex Statement index.
1550 /// @param bind Pointer to an array of PgSQL bindings.
1551 /// @param exchange Pointer to the exchange object used for the
1552 /// particular query.
1553 /// @param [out] result Reference to the collection of hosts returned.
1554 /// @param single A boolean value indicating if a single host is
1555 /// expected to be returned, or multiple hosts.
1556 void getHostCollection(PgSqlHostContextPtr& ctx,
1557 StatementIndex stindex,
1558 PsqlBindArrayPtr bind,
1559 boost::shared_ptr<PgSqlHostExchange> exchange,
1560 ConstHostCollection& result,
1561 bool single) const;
1562
1563 /// @brief Retrieves a host by subnet and client's unique identifier.
1564 ///
1565 /// This method is used by both PgSqlHostDataSource::get4 and
1566 /// PgSqlHostDataSource::get6 methods.
1567 ///
1568 /// @param ctx Context
1569 /// @param subnet_id Subnet identifier.
1570 /// @param identifier_type Identifier type.
1571 /// @param identifier_begin Pointer to a beginning of a buffer containing
1572 /// an identifier.
1573 /// @param identifier_len Identifier length.
1574 /// @param stindex Statement index.
1575 /// @param exchange Pointer to the exchange object used for the
1576 /// particular query.
1577 ///
1578 /// @return Pointer to const instance of Host or null pointer if
1579 /// no host found.
1580 ConstHostPtr getHost(PgSqlHostContextPtr& ctx,
1581 const SubnetID& subnet_id,
1582 const Host::IdentifierType& identifier_type,
1583 const uint8_t* identifier_begin,
1584 const size_t identifier_len,
1585 StatementIndex stindex,
1586 boost::shared_ptr<PgSqlHostExchange> exchange) const;
1587
1588 /// @brief Throws exception if database is read only.
1589 ///
1590 /// This method should be called by the methods which write to the
1591 /// database. If the backend is operating in read-only mode this
1592 /// method will throw exception.
1593 ///
1594 /// @param ctx Context
1595 ///
1596 /// @throw DbReadOnly if backend is operating in read only mode.
1597 void checkReadOnly(PgSqlHostContextPtr& ctx) const;
1598
1599 /// @brief Returns PostgreSQL schema version of the open database
1600 ///
1601 /// @return Version number stored in the database, as a pair of unsigned
1602 /// integers. "first" is the major version number, "second" the
1603 /// minor number.
1604 ///
1605 /// @throw isc::db::DbOperationError An operation on the open database
1606 /// has failed.
1607 std::pair<uint32_t, uint32_t> getVersion() const;
1608
1609 /// @brief The parameters
1610 DatabaseConnection::ParameterMap parameters_;
1611
1612 /// @brief Holds the setting whether the IP reservations must be unique or
1613 /// may be non-unique.
1614 bool ip_reservations_unique_;
1615
1616 /// @brief The pool of contexts
1617 PgSqlHostContextPoolPtr pool_;
1618
1619 /// @brief Indicates if there is at least one connection that can no longer
1620 /// be used for normal operations.
1621 bool unusable_;
1622
1623 /// @brief Timer name used to register database reconnect timer.
1624 std::string timer_name_;
1625 };
1626
1627 namespace {
1628
1629 /// @brief Array of tagged statements.
1630 typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS>
1631 TaggedStatementArray;
1632
1633 /// @brief Prepared PosgreSQL statements used by the backend to insert and
1634 /// retrieve reservation data from the database.
1635 TaggedStatementArray tagged_statements = { {
1636 // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
1637 // Retrieves host information, IPv6 reservations and both DHCPv4 and
1638 // DHCPv6 options associated with the host. The LEFT JOIN clause is used
1639 // to retrieve information from 4 different tables using a single query.
1640 // Hence, this query returns multiple rows for a single host.
1641 {2,
1642 { OID_BYTEA, OID_INT2 },
1643 "get_host_dhcpid",
1644 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1645 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1646 " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1647 " h.user_context, "
1648 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1649 " h.dhcp4_boot_file_name, h.auth_key, "
1650 " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1651 " o4.persistent, o4.user_context, "
1652 " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1653 " o6.persistent, o6.user_context, "
1654 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1655 "FROM hosts AS h "
1656 "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1657 "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1658 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1659 "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
1660 "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1661 },
1662
1663 // PgSqlHostDataSourceImpl::GET_HOST_ADDR
1664 // Retrieves host information along with the DHCPv4 options associated with
1665 // it. Left joining the dhcp4_options table results in multiple rows being
1666 // returned for the same host. The host is retrieved by IPv4 address.
1667 {1,
1668 { OID_INT8 },
1669 "get_host_addr",
1670 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1671 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1672 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1673 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1674 " h.dhcp4_boot_file_name, h.auth_key, "
1675 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1676 " o.persistent, o.user_context "
1677 "FROM hosts AS h "
1678 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1679 "WHERE ipv4_address = $1 "
1680 "ORDER BY h.host_id, o.option_id"
1681 },
1682
1683 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
1684 // Retrieves host information and DHCPv4 options using subnet identifier
1685 // and client's identifier. Left joining the dhcp4_options table results in
1686 // multiple rows being returned for the same host.
1687 {3,
1688 { OID_INT8, OID_INT2, OID_BYTEA },
1689 "get_host_subid4_dhcpid",
1690 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1691 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1692 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1693 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1694 " h.dhcp4_boot_file_name, h.auth_key, "
1695 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1696 " o.persistent, o.user_context "
1697 "FROM hosts AS h "
1698 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1699 "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1700 " AND h.dhcp_identifier = $3 "
1701 "ORDER BY h.host_id, o.option_id"
1702 },
1703
1704 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
1705 // Retrieves host information, IPv6 reservations and DHCPv6 options
1706 // associated with a host. The number of rows returned is a multiplication
1707 // of number of IPv6 reservations and DHCPv6 options.
1708 {3,
1709 { OID_INT8, OID_INT2, OID_BYTEA },
1710 "get_host_subid6_dhcpid",
1711 "SELECT h.host_id, h.dhcp_identifier, "
1712 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1713 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1714 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1715 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1716 " h.dhcp4_boot_file_name, h.auth_key, "
1717 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1718 " o.persistent, o.user_context, "
1719 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1720 "FROM hosts AS h "
1721 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1722 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1723 "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
1724 " AND h.dhcp_identifier = $3 "
1725 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1726 },
1727
1728 // PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
1729 // Retrieves host information and DHCPv4 options for the host using subnet
1730 // identifier and IPv4 reservation. Left joining the dhcp4_options table
1731 // results in multiple rows being returned for the host. The number of
1732 // rows depends on the number of options defined for the host.
1733 {2,
1734 { OID_INT8, OID_INT8 },
1735 "get_host_subid_addr",
1736 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1737 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1738 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1739 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1740 " h.dhcp4_boot_file_name, h.auth_key, "
1741 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1742 " o.persistent, o.user_context "
1743 "FROM hosts AS h "
1744 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1745 "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
1746 "ORDER BY h.host_id, o.option_id"
1747 },
1748
1749 // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
1750 // Retrieves host information, IPv6 reservations and DHCPv6 options
1751 // associated with a host using prefix and prefix length. This query
1752 // returns host information for a single host. However, multiple rows
1753 // are returned due to left joining IPv6 reservations and DHCPv6 options.
1754 // The number of rows returned is multiplication of number of existing
1755 // IPv6 reservations and DHCPv6 options.
1756 {2,
1757 { OID_VARCHAR, OID_INT2 },
1758 "get_host_prefix",
1759 "SELECT h.host_id, h.dhcp_identifier, "
1760 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1761 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1762 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1763 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1764 " h.dhcp4_boot_file_name, h.auth_key, "
1765 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1766 " o.persistent, o.user_context, "
1767 " r.reservation_id, r.address, r.prefix_len, r.type, "
1768 " r.dhcp6_iaid "
1769 "FROM hosts AS h "
1770 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1771 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1772 "WHERE h.host_id = "
1773 " (SELECT host_id FROM ipv6_reservations "
1774 " WHERE address = $1 AND prefix_len = $2) "
1775 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1776 },
1777
1778 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR
1779 // Retrieves host information, IPv6 reservations and DHCPv6 options
1780 // associated with a host using IPv6 subnet id and prefix. This query
1781 // returns host information for a single host. However, multiple rows
1782 // are returned due to left joining IPv6 reservations and DHCPv6 options.
1783 // The number of rows returned is multiplication of number of existing
1784 // IPv6 reservations and DHCPv6 options.
1785 {2,
1786 { OID_INT8, OID_VARCHAR },
1787 "get_host_subid6_addr",
1788 "SELECT h.host_id, h.dhcp_identifier, "
1789 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1790 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1791 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1792 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1793 " h.dhcp4_boot_file_name, h.auth_key, "
1794 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1795 " o.persistent, o.user_context, "
1796 " r.reservation_id, r.address, r.prefix_len, r.type, "
1797 " r.dhcp6_iaid "
1798 "FROM hosts AS h "
1799 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1800 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1801 "WHERE h.dhcp6_subnet_id = $1 AND r.address = $2 "
1802 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1803 },
1804
1805 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4
1806 //
1807 // Retrieves host information for all hosts in a subnet, along with the
1808 // DHCPv4 options associated with it. Left joining the dhcp4_options table
1809 // results in multiple rows being returned for the same host. The hosts are
1810 // retrieved by subnet id.
1811 {1,
1812 { OID_INT8 },
1813 "get_host_subid4",
1814 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1815 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1816 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1817 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1818 " h.dhcp4_boot_file_name, h.auth_key, "
1819 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1820 " o.persistent, o.user_context "
1821 "FROM hosts AS h "
1822 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1823 "WHERE h.dhcp4_subnet_id = $1 "
1824 "ORDER BY h.host_id, o.option_id"
1825 },
1826
1827 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6
1828 //
1829 // Retrieves host information, IPv6 reservations and DHCPv6 options
1830 // associated with all hosts using the IPv6 subnet id. This query returns
1831 // host information for many hosts. However, multiple rows are
1832 // returned due to left joining IPv6 reservations and DHCPv6 options.
1833 // The number of rows returned is multiplication of number of existing
1834 // IPv6 reservations and DHCPv6 options for each host in a subnet. There
1835 // are usually many hosts in a subnet. The amount of returned data may
1836 // be huge.
1837 {1,
1838 { OID_INT8 },
1839 "get_host_subid6",
1840 "SELECT h.host_id, h.dhcp_identifier, "
1841 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1842 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1843 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1844 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1845 " h.dhcp4_boot_file_name, h.auth_key, "
1846 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1847 " o.persistent, o.user_context, "
1848 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1849 "FROM hosts AS h "
1850 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1851 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1852 "WHERE h.dhcp6_subnet_id = $1 "
1853 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1854 },
1855
1856 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME
1857 // Retrieves host information, IPv6 reservations and both DHCPv4 and
1858 // DHCPv6 options associated with all hosts using the hostname.
1859 // The LEFT JOIN clause is used to retrieve information from 4 different
1860 // tables using a single query. Hence, this query returns multiple rows
1861 // for a single host.
1862 {1,
1863 { OID_VARCHAR },
1864 "get_host_hostname",
1865 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1866 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
1867 " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
1868 " h.user_context, "
1869 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1870 " h.dhcp4_boot_file_name, h.auth_key, "
1871 " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
1872 " o4.persistent, o4.user_context, "
1873 " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
1874 " o6.persistent, o6.user_context, "
1875 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1876 "FROM hosts AS h "
1877 "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
1878 "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
1879 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1880 "WHERE lower(h.hostname) = $1 "
1881 "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
1882 },
1883
1884 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4
1885 // Retrieves host information for all hosts with a hostname in a subnet,
1886 // along with the DHCPv4 options associated with it. Left joining
1887 // the dhcp4_options table results in multiple rows being returned for
1888 // the same host.
1889 {2,
1890 { OID_VARCHAR, OID_INT8 },
1891 "get_host_hostname_subid4",
1892 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1893 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1894 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1895 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1896 " h.dhcp4_boot_file_name, h.auth_key, "
1897 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1898 " o.persistent, o.user_context "
1899 "FROM hosts AS h "
1900 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1901 "WHERE lower(h.hostname) = $1 AND h.dhcp4_subnet_id = $2 "
1902 "ORDER BY h.host_id, o.option_id"
1903 },
1904
1905 // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6
1906 // Retrieves host information, IPv6 reservations and DHCPv6 options
1907 // associated with all hosts using the hostname and the IPv6 subnet id.
1908 // This query returns host information for many hosts. However, multiple
1909 // rows are returned due to left joining IPv6 reservations and DHCPv6
1910 // options. The number of rows returned is multiplication of number of
1911 // existing IPv6 reservations and DHCPv6 options for each host in a subnet.
1912 {2,
1913 { OID_VARCHAR, OID_INT8 },
1914 "get_host_hostname_subid6",
1915 "SELECT h.host_id, h.dhcp_identifier, "
1916 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1917 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1918 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1919 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1920 " h.dhcp4_boot_file_name, h.auth_key, "
1921 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1922 " o.persistent, o.user_context, "
1923 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1924 "FROM hosts AS h "
1925 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1926 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1927 "WHERE lower(h.hostname) = $1 AND h.dhcp6_subnet_id = $2 "
1928 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1929 },
1930
1931 // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE
1932 // Retrieves host information along with the DHCPv4 options associated with
1933 // it. Left joining the dhcp4_options table results in multiple rows being
1934 // returned for the same host. The hosts are retrieved by subnet id,
1935 // starting from specified host id. Specified number of hosts is returned.
1936 {3,
1937 { OID_INT8, OID_INT8, OID_INT8 },
1938 "get_host_subid4_page",
1939 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1940 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1941 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1942 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1943 " h.dhcp4_boot_file_name, h.auth_key, "
1944 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1945 " o.persistent, o.user_context "
1946 "FROM ( SELECT * FROM hosts AS h "
1947 " WHERE h.dhcp4_subnet_id = $1 AND h.host_id > $2 "
1948 " ORDER BY h.host_id "
1949 " LIMIT $3 ) AS h "
1950 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
1951 "ORDER BY h.host_id, o.option_id"
1952 },
1953
1954 // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE
1955 // Retrieves host information, IPv6 reservations and DHCPv6 options
1956 // associated with a host using IPv6 subnet id. This query returns
1957 // host information for a single host. However, multiple rows are
1958 // returned due to left joining IPv6 reservations and DHCPv6 options.
1959 // The number of rows returned is multiplication of number of existing
1960 // IPv6 reservations and DHCPv6 options.
1961 {3,
1962 { OID_INT8, OID_INT8, OID_INT8 },
1963 "get_host_subid6_page",
1964 "SELECT h.host_id, h.dhcp_identifier, "
1965 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
1966 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1967 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1968 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1969 " h.dhcp4_boot_file_name, h.auth_key, "
1970 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1971 " o.persistent, o.user_context, "
1972 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
1973 "FROM ( SELECT * FROM hosts AS h "
1974 " WHERE h.dhcp6_subnet_id = $1 AND h.host_id > $2 "
1975 " ORDER BY h.host_id "
1976 " LIMIT $3 ) AS h "
1977 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
1978 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
1979 "ORDER BY h.host_id, o.option_id, r.reservation_id"
1980 },
1981
1982 // PgSqlHostDataSourceImpl::GET_HOST_PAGE4
1983 // Retrieves host information along with the DHCPv4 options associated with
1984 // it. Left joining the dhcp4_options table results in multiple rows being
1985 // returned for the same host. The hosts are retrieved starting from
1986 // specified host id. Specified number of hosts is returned.
1987 {2,
1988 { OID_INT8, OID_INT8 },
1989 "get_host_page4",
1990 "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
1991 " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
1992 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
1993 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
1994 " h.dhcp4_boot_file_name, h.auth_key, "
1995 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
1996 " o.persistent, o.user_context "
1997 "FROM ( SELECT * FROM hosts AS h "
1998 " WHERE h.host_id > $1 "
1999 " ORDER BY h.host_id "
2000 " LIMIT $2 ) AS h "
2001 "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
2002 "ORDER BY h.host_id, o.option_id"
2003 },
2004
2005 // PgSqlHostDataSourceImpl::GET_HOST_PAGE6
2006 // Retrieves host information, IPv6 reservations and DHCPv6 options
2007 // associated with a host using IPv6 subnet id. This query returns
2008 // host information for a single host. However, multiple rows are
2009 // returned due to left joining IPv6 reservations and DHCPv6 options.
2010 // The number of rows returned is multiplication of number of existing
2011 // IPv6 reservations and DHCPv6 options.
2012 {2,
2013 { OID_INT8, OID_INT8 },
2014 "get_host_page6",
2015 "SELECT h.host_id, h.dhcp_identifier, "
2016 " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
2017 " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
2018 " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
2019 " h.dhcp4_next_server, h.dhcp4_server_hostname, "
2020 " h.dhcp4_boot_file_name, h.auth_key, "
2021 " o.option_id, o.code, o.value, o.formatted_value, o.space, "
2022 " o.persistent, o.user_context, "
2023 " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
2024 "FROM ( SELECT * FROM hosts AS h "
2025 " WHERE h.host_id > $1 "
2026 " ORDER BY h.host_id "
2027 " LIMIT $2 ) AS h "
2028 "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
2029 "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
2030 "ORDER BY h.host_id, o.option_id, r.reservation_id"
2031 },
2032
2033 // PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP
2034 // Inserts a host into the 'hosts' table without checking that there is
2035 // a reservation for the IP address.
2036 {13,
2037 { OID_BYTEA, OID_INT2,
2038 OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
2039 OID_VARCHAR, OID_VARCHAR, OID_TEXT,
2040 OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR},
2041 "insert_host_non_unique_ip",
2042 "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2043 " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2044 " dhcp4_client_classes, dhcp6_client_classes, user_context, "
2045 " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2046 "VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) "
2047 "RETURNING host_id"
2048 },
2049
2050 // PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP
2051 // Inserts a host into the 'hosts' table with checking that reserved IP
2052 // address is unique. The innermost query checks if there is at least
2053 // one host for the given IP/subnet combination. For checking whether
2054 // hosts exists or not it doesn't matter if we select actual columns,
2055 // thus SELECT 1 was used as an optimization to avoid selecting data
2056 // that will be ignored anyway. If it does not exist the new host is
2057 // inserted. If the host with the given IP address already exists the
2058 // new host won't be inserted. The caller can check the number of
2059 // affected rows to detect that there was a duplicate host in the
2060 // database. Returns the inserted host id.
2061 {15,
2062 { OID_BYTEA, OID_INT2,
2063 OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
2064 OID_VARCHAR, OID_VARCHAR, OID_TEXT,
2065 OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR,
2066 OID_INT8, OID_INT8},
2067 "insert_host_unique_ip",
2068 "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
2069 " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
2070 " dhcp4_client_classes, dhcp6_client_classes, user_context, "
2071 " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
2072 " SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
2073 " WHERE NOT EXISTS ("
2074 " SELECT 1 FROM hosts WHERE ipv4_address = $14 AND dhcp4_subnet_id = $15"
2075 " LIMIT 1"
2076 " ) "
2077 "RETURNING host_id"
2078 },
2079
2080 // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE
2081 // Inserts a single IPv6 reservation into 'reservations' table without
2082 // checking that the inserted reservation is unique.
2083 {5,
2084 { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
2085 "insert_v6_resrv_non_unique",
2086 "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2087 " dhcp6_iaid, host_id) "
2088 "VALUES ($1, $2, $3, $4, $5)"
2089 },
2090
2091 // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE
2092 // Inserts a single IPv6 reservation into 'reservations' table with
2093 // checking that the inserted reservation is unique.
2094 {7,
2095 { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4, OID_VARCHAR, OID_INT2 },
2096 "insert_v6_resrv_unique",
2097 "INSERT INTO ipv6_reservations(address, prefix_len, type, "
2098 " dhcp6_iaid, host_id) "
2099 "SELECT $1, $2, $3, $4, $5 "
2100 " WHERE NOT EXISTS ("
2101 " SELECT 1 FROM ipv6_reservations"
2102 " WHERE address = $6 AND prefix_len = $7"
2103 " LIMIT 1"
2104 " )"
2105 },
2106
2107 // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
2108 // Inserts a single DHCPv4 option into 'dhcp4_options' table.
2109 // Using fixed scope_id = 3, which associates an option with host.
2110 {7,
2111 { OID_INT2, OID_BYTEA, OID_TEXT,
2112 OID_VARCHAR, OID_BOOL, OID_TEXT, OID_INT8 },
2113 "insert_v4_host_option",
2114 "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
2115 " persistent, user_context, host_id, scope_id) "
2116 "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2117 },
2118
2119 // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
2120 // Inserts a single DHCPv6 option into 'dhcp6_options' table.
2121 // Using fixed scope_id = 3, which associates an option with host.
2122 {7,
2123 { OID_INT2, OID_BYTEA, OID_TEXT,
2124 OID_VARCHAR, OID_BOOL, OID_TEXT, OID_INT8 },
2125 "insert_v6_host_option",
2126 "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
2127 " persistent, user_context, host_id, scope_id) "
2128 "VALUES ($1, $2, $3, $4, $5, $6, $7, 3)"
2129 },
2130
2131 // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
2132 // Deletes a v4 host that matches (subnet-id, addr4)
2133 {2,
2134 { OID_INT8, OID_INT8 },
2135 "del_host_addr4",
2136 "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
2137 },
2138
2139 // PgSqlHostDataSourceImpl::DEL_HOST_ADDR6
2140 // Deletes a v6 host that matches (subnet-id, addr6)
2141 {2,
2142 { OID_INT8, OID_VARCHAR },
2143 "del_host_addr6",
2144 "DELETE FROM hosts USING ipv6_reservations "
2145 " WHERE dhcp6_subnet_id = $1 AND ipv6_reservations.address = $2"
2146 },
2147
2148 // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
2149 // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
2150 {3,
2151 { OID_INT8, OID_INT2, OID_BYTEA },
2152 "del_host_subid4_id",
2153 "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
2154 "AND dhcp_identifier_type = $2 "
2155 "AND dhcp_identifier = $3"
2156 },
2157
2158 // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
2159 // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
2160 {3,
2161 { OID_INT8, OID_INT2, OID_BYTEA },
2162 "del_host_subid6_id",
2163 "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
2164 "AND dhcp_identifier_type = $2 "
2165 "AND dhcp_identifier = $3"
2166 }
2167 }
2168 };
2169
2170 } // namespace
2171
2172 // PgSqlHostContext Constructor
2173
PgSqlHostContext(const DatabaseConnection::ParameterMap & parameters,IOServiceAccessorPtr io_service_accessor,db::DbCallback db_reconnect_callback)2174 PgSqlHostContext::PgSqlHostContext(const DatabaseConnection::ParameterMap& parameters,
2175 IOServiceAccessorPtr io_service_accessor,
2176 db::DbCallback db_reconnect_callback)
2177 : conn_(parameters, io_service_accessor, db_reconnect_callback),
2178 is_readonly_(true) {
2179 }
2180
2181 // PgSqlHostContextAlloc Constructor and Destructor
2182
PgSqlHostContextAlloc(PgSqlHostDataSourceImpl & mgr)2183 PgSqlHostDataSource::PgSqlHostContextAlloc::PgSqlHostContextAlloc(
2184 PgSqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
2185
2186 if (MultiThreadingMgr::instance().getMode()) {
2187 // multi-threaded
2188 {
2189 // we need to protect the whole pool_ operation, hence extra scope {}
2190 lock_guard<mutex> lock(mgr_.pool_->mutex_);
2191 if (!mgr_.pool_->pool_.empty()) {
2192 ctx_ = mgr_.pool_->pool_.back();
2193 mgr_.pool_->pool_.pop_back();
2194 }
2195 }
2196 if (!ctx_) {
2197 ctx_ = mgr_.createContext();
2198 }
2199 } else {
2200 // single-threaded
2201 if (mgr_.pool_->pool_.empty()) {
2202 isc_throw(Unexpected, "No available PostgreSQL host context?!");
2203 }
2204 ctx_ = mgr_.pool_->pool_.back();
2205 }
2206 }
2207
~PgSqlHostContextAlloc()2208 PgSqlHostDataSource::PgSqlHostContextAlloc::~PgSqlHostContextAlloc() {
2209 if (MultiThreadingMgr::instance().getMode()) {
2210 // multi-threaded
2211 lock_guard<mutex> lock(mgr_.pool_->mutex_);
2212 mgr_.pool_->pool_.push_back(ctx_);
2213 if (ctx_->conn_.isUnusable()) {
2214 mgr_.unusable_ = true;
2215 }
2216 } else if (ctx_->conn_.isUnusable()) {
2217 mgr_.unusable_ = true;
2218 }
2219 }
2220
PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap & parameters)2221 PgSqlHostDataSourceImpl::PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters)
2222 : parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
2223 timer_name_("") {
2224
2225 // Create unique timer name per instance.
2226 timer_name_ = "PgSqlHostMgr[";
2227 timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
2228 timer_name_ += "]DbReconnectTimer";
2229
2230 // Validate the schema version first.
2231 std::pair<uint32_t, uint32_t> code_version(PG_SCHEMA_VERSION_MAJOR,
2232 PG_SCHEMA_VERSION_MINOR);
2233 std::pair<uint32_t, uint32_t> db_version = getVersion();
2234 if (code_version != db_version) {
2235 isc_throw(DbOpenError,
2236 "PostgreSQL schema version mismatch: need version: "
2237 << code_version.first << "." << code_version.second
2238 << " found version: " << db_version.first << "."
2239 << db_version.second);
2240 }
2241
2242 // Create an initial context.
2243 pool_.reset(new PgSqlHostContextPool());
2244 pool_->pool_.push_back(createContext());
2245 }
2246
2247 // Create context.
2248
2249 PgSqlHostContextPtr
createContext() const2250 PgSqlHostDataSourceImpl::createContext() const {
2251 PgSqlHostContextPtr ctx(new PgSqlHostContext(parameters_,
2252 IOServiceAccessorPtr(new IOServiceAccessor(&HostMgr::getIOService)),
2253 &PgSqlHostDataSourceImpl::dbReconnect));
2254
2255 // Open the database.
2256 ctx->conn_.openDatabase();
2257
2258 // Now prepare the SQL statements.
2259 ctx->conn_.prepareStatements(tagged_statements.begin(),
2260 tagged_statements.begin() + WRITE_STMTS_BEGIN);
2261
2262 // Check if the backend is explicitly configured to operate with
2263 // read only access to the database.
2264 ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
2265
2266 // If we are using read-write mode for the database we also prepare
2267 // statements for INSERTS etc.
2268 if (!ctx->is_readonly_) {
2269 ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
2270 tagged_statements.end());
2271 } else {
2272 LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_READONLY);
2273 }
2274
2275 ctx->host_ipv4_exchange_.reset(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY));
2276 ctx->host_ipv6_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY));
2277 ctx->host_ipv46_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
2278 ctx->host_ipv6_reservation_exchange_.reset(new PgSqlIPv6ReservationExchange());
2279 ctx->host_option_exchange_.reset(new PgSqlOptionExchange());
2280
2281 // Create ReconnectCtl for this connection.
2282 ctx->conn_.makeReconnectCtl(timer_name_);
2283
2284 return (ctx);
2285 }
2286
~PgSqlHostDataSourceImpl()2287 PgSqlHostDataSourceImpl::~PgSqlHostDataSourceImpl() {
2288 }
2289
2290 bool
dbReconnect(ReconnectCtlPtr db_reconnect_ctl)2291 PgSqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
2292 MultiThreadingCriticalSection cs;
2293
2294 // Invoke application layer connection lost callback.
2295 if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
2296 return (false);
2297 }
2298
2299 bool reopened = false;
2300
2301 const std::string timer_name = db_reconnect_ctl->timerName();
2302
2303 // At least one connection was lost.
2304 try {
2305 CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
2306 std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
2307 for (std::string& hds : host_db_access_list) {
2308 auto parameters = DatabaseConnection::parse(hds);
2309 if (HostMgr::delBackend("postgresql", hds, true)) {
2310 HostMgr::addBackend(hds);
2311 }
2312 }
2313 reopened = true;
2314 } catch (const std::exception& ex) {
2315 LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED)
2316 .arg(ex.what());
2317 }
2318
2319 if (reopened) {
2320 // Cancel the timer.
2321 if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2322 TimerMgr::instance()->unregisterTimer(timer_name);
2323 }
2324
2325 // Invoke application layer connection recovered callback.
2326 if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
2327 return (false);
2328 }
2329 } else {
2330 if (!db_reconnect_ctl->checkRetries()) {
2331 // We're out of retries, log it and initiate shutdown.
2332 LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED)
2333 .arg(db_reconnect_ctl->maxRetries());
2334
2335 // Cancel the timer.
2336 if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
2337 TimerMgr::instance()->unregisterTimer(timer_name);
2338 }
2339
2340 // Invoke application layer connection failed callback.
2341 DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
2342 return (false);
2343 }
2344
2345 LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE)
2346 .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
2347 .arg(db_reconnect_ctl->maxRetries())
2348 .arg(db_reconnect_ctl->retryInterval());
2349
2350 // Start the timer.
2351 if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
2352 TimerMgr::instance()->registerTimer(timer_name,
2353 std::bind(&PgSqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
2354 db_reconnect_ctl->retryInterval(),
2355 asiolink::IntervalTimer::ONE_SHOT);
2356 }
2357 TimerMgr::instance()->setup(timer_name);
2358 }
2359
2360 return (true);
2361 }
2362
2363 uint64_t
addStatement(PgSqlHostContextPtr & ctx,StatementIndex stindex,PsqlBindArrayPtr & bind_array,const bool return_last_id)2364 PgSqlHostDataSourceImpl::addStatement(PgSqlHostContextPtr& ctx,
2365 StatementIndex stindex,
2366 PsqlBindArrayPtr& bind_array,
2367 const bool return_last_id) {
2368 uint64_t last_id = 0;
2369 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2370 tagged_statements[stindex].nbparams,
2371 &bind_array->values_[0],
2372 &bind_array->lengths_[0],
2373 &bind_array->formats_[0], 0));
2374
2375 int s = PQresultStatus(r);
2376
2377 if (s != PGRES_COMMAND_OK) {
2378 // Failure: check for the special case of duplicate entry.
2379 if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
2380 isc_throw(DuplicateEntry, "Database duplicate entry error");
2381 }
2382
2383 // Connection determines if the error is fatal or not, and
2384 // throws the appropriate exception
2385 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2386 }
2387
2388 // Get the number of affected rows.
2389 char* rows_affected = PQcmdTuples(r);
2390 if (!rows_affected) {
2391 isc_throw(DbOperationError,
2392 "Could not retrieve the number of affected rows.");
2393 }
2394
2395 // If the number of rows inserted is 0 it means that the query detected
2396 // an attempt to insert duplicated data for which there is no unique
2397 // index in the database. Unique indexes are not created in the database
2398 // when it may be sometimes allowed to insert duplicated records per
2399 // server's configuration.
2400 if (rows_affected[0] == '0') {
2401 isc_throw(DuplicateEntry, "Database duplicate entry error");
2402 }
2403
2404 if (return_last_id) {
2405 PgSqlExchange::getColumnValue(r, 0, 0, last_id);
2406 }
2407
2408 return (last_id);
2409 }
2410
2411 bool
delStatement(PgSqlHostContextPtr & ctx,StatementIndex stindex,PsqlBindArrayPtr & bind_array)2412 PgSqlHostDataSourceImpl::delStatement(PgSqlHostContextPtr& ctx,
2413 StatementIndex stindex,
2414 PsqlBindArrayPtr& bind_array) {
2415 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2416 tagged_statements[stindex].nbparams,
2417 &bind_array->values_[0],
2418 &bind_array->lengths_[0],
2419 &bind_array->formats_[0], 0));
2420
2421 int s = PQresultStatus(r);
2422
2423 if (s != PGRES_COMMAND_OK) {
2424 // Connection determines if the error is fatal or not, and
2425 // throws the appropriate exception
2426 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2427 }
2428
2429 // Now check how many rows (hosts) were deleted. This should be either
2430 // "0" or "1".
2431 char* rows_deleted = PQcmdTuples(r);
2432 if (!rows_deleted) {
2433 isc_throw(DbOperationError,
2434 "Could not retrieve the number of deleted rows.");
2435 }
2436 return (rows_deleted[0] != '0');
2437 }
2438
2439 void
addResv(PgSqlHostContextPtr & ctx,const IPv6Resrv & resv,const HostID & id)2440 PgSqlHostDataSourceImpl::addResv(PgSqlHostContextPtr& ctx,
2441 const IPv6Resrv& resv,
2442 const HostID& id) {
2443 PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->
2444 createBindForSend(resv, id, ip_reservations_unique_);
2445
2446 addStatement(ctx,
2447 ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE,
2448 bind_array);
2449 }
2450
2451 void
addOption(PgSqlHostContextPtr & ctx,const StatementIndex & stindex,const OptionDescriptor & opt_desc,const std::string & opt_space,const Optional<SubnetID> &,const HostID & id)2452 PgSqlHostDataSourceImpl::addOption(PgSqlHostContextPtr& ctx,
2453 const StatementIndex& stindex,
2454 const OptionDescriptor& opt_desc,
2455 const std::string& opt_space,
2456 const Optional<SubnetID>&,
2457 const HostID& id) {
2458 PsqlBindArrayPtr bind_array = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, id);
2459
2460 addStatement(ctx, stindex, bind_array);
2461 }
2462
2463 void
addOptions(PgSqlHostContextPtr & ctx,const StatementIndex & stindex,const ConstCfgOptionPtr & options_cfg,const uint64_t host_id)2464 PgSqlHostDataSourceImpl::addOptions(PgSqlHostContextPtr& ctx,
2465 const StatementIndex& stindex,
2466 const ConstCfgOptionPtr& options_cfg,
2467 const uint64_t host_id) {
2468 // Get option space names and vendor space names and combine them within a
2469 // single list.
2470 std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
2471 std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
2472 option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
2473 vendor_spaces.end());
2474
2475 // For each option space retrieve all options and insert them into the
2476 // database.
2477 for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
2478 OptionContainerPtr options = options_cfg->getAll(*space);
2479 if (options && !options->empty()) {
2480 for (auto opt = options->begin(); opt != options->end(); ++opt) {
2481 addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
2482 }
2483 }
2484 }
2485 }
2486
2487 void
getHostCollection(PgSqlHostContextPtr & ctx,StatementIndex stindex,PsqlBindArrayPtr bind_array,boost::shared_ptr<PgSqlHostExchange> exchange,ConstHostCollection & result,bool single) const2488 PgSqlHostDataSourceImpl::getHostCollection(PgSqlHostContextPtr& ctx,
2489 StatementIndex stindex,
2490 PsqlBindArrayPtr bind_array,
2491 boost::shared_ptr<PgSqlHostExchange> exchange,
2492 ConstHostCollection& result,
2493 bool single) const {
2494
2495 exchange->clear();
2496 PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
2497 tagged_statements[stindex].nbparams,
2498 &bind_array->values_[0],
2499 &bind_array->lengths_[0],
2500 &bind_array->formats_[0], 0));
2501
2502 ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
2503
2504 int rows = r.getRows();
2505 for (int row = 0; row < rows; ++row) {
2506 exchange->processRowData(result, r, row);
2507
2508 if (single && result.size() > 1) {
2509 isc_throw(MultipleRecords, "multiple records were found in the "
2510 "database where only one was expected for query "
2511 << tagged_statements[stindex].name);
2512 }
2513 }
2514 }
2515
2516 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) const2517 PgSqlHostDataSourceImpl::getHost(PgSqlHostContextPtr& ctx,
2518 const SubnetID& subnet_id,
2519 const Host::IdentifierType& identifier_type,
2520 const uint8_t* identifier_begin,
2521 const size_t identifier_len,
2522 StatementIndex stindex,
2523 boost::shared_ptr<PgSqlHostExchange> exchange) const {
2524
2525 // Set up the WHERE clause value
2526 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2527
2528 // Add the subnet id.
2529 bind_array->add(subnet_id);
2530
2531 // Add the Identifier type.
2532 bind_array->add(static_cast<uint8_t>(identifier_type));
2533
2534 // Add the identifier value.
2535 bind_array->add(identifier_begin, identifier_len);
2536
2537 ConstHostCollection collection;
2538 getHostCollection(ctx, stindex, bind_array, exchange, collection, true);
2539
2540 // Return single record if present, else clear the host.
2541 ConstHostPtr result;
2542 if (!collection.empty()) {
2543 result = *collection.begin();
2544 }
2545
2546 return (result);
2547 }
2548
2549 std::pair<uint32_t, uint32_t>
getVersion() const2550 PgSqlHostDataSourceImpl::getVersion() const {
2551 LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
2552 DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
2553 return (PgSqlConnection::getVersion(parameters_));
2554 }
2555
2556 void
checkReadOnly(PgSqlHostContextPtr & ctx) const2557 PgSqlHostDataSourceImpl::checkReadOnly(PgSqlHostContextPtr& ctx) const {
2558 if (ctx->is_readonly_) {
2559 isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
2560 " to operate in read only mode");
2561 }
2562 }
2563
2564 /*********** PgSqlHostDataSource *********************/
2565
PgSqlHostDataSource(const DatabaseConnection::ParameterMap & parameters)2566 PgSqlHostDataSource::PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters)
2567 : impl_(new PgSqlHostDataSourceImpl(parameters)) {
2568 }
2569
~PgSqlHostDataSource()2570 PgSqlHostDataSource::~PgSqlHostDataSource() {
2571 }
2572
2573 DatabaseConnection::ParameterMap
getParameters() const2574 PgSqlHostDataSource::getParameters() const {
2575 return (impl_->parameters_);
2576 }
2577
2578 void
add(const HostPtr & host)2579 PgSqlHostDataSource::add(const HostPtr& host) {
2580 // Get a context
2581 PgSqlHostContextAlloc get_context(*impl_);
2582 PgSqlHostContextPtr ctx = get_context.ctx_;
2583
2584 // If operating in read-only mode, throw exception.
2585 impl_->checkReadOnly(ctx);
2586
2587 // Initiate PostgreSQL transaction as we will have to make multiple queries
2588 // to insert host information into multiple tables. If that fails on
2589 // any stage, the transaction will be rolled back by the destructor of
2590 // the PgSqlTransaction class.
2591 PgSqlTransaction transaction(ctx->conn_);
2592
2593 // If we're configured to check that an IP reservation within a given subnet
2594 // is unique, the IP reservation exists and the subnet is actually set
2595 // we will be using a special query that checks for uniqueness. Otherwise,
2596 // we will use a regular insert statement.
2597 bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
2598 && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
2599
2600 // Create the PgSQL Bind array for the host
2601 PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
2602
2603 // ... and insert the host.
2604 uint32_t host_id = impl_->addStatement(ctx,
2605 unique_ip ? PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
2606 PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
2607 bind_array, true);
2608
2609 // Insert DHCPv4 options.
2610 ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
2611 if (cfg_option4) {
2612 impl_->addOptions(ctx, PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
2613 cfg_option4, host_id);
2614 }
2615
2616 // Insert DHCPv6 options.
2617 ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
2618 if (cfg_option6) {
2619 impl_->addOptions(ctx, PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
2620 cfg_option6, host_id);
2621 }
2622
2623 // Insert IPv6 reservations.
2624 IPv6ResrvRange v6resv = host->getIPv6Reservations();
2625 if (std::distance(v6resv.first, v6resv.second) > 0) {
2626 for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
2627 ++resv) {
2628 impl_->addResv(ctx, resv->second, host_id);
2629 }
2630 }
2631
2632 // Everything went fine, so explicitly commit the transaction.
2633 transaction.commit();
2634 }
2635
2636 bool
del(const SubnetID & subnet_id,const asiolink::IOAddress & addr)2637 PgSqlHostDataSource::del(const SubnetID& subnet_id,
2638 const asiolink::IOAddress& addr) {
2639 // Get a context
2640 PgSqlHostContextAlloc get_context(*impl_);
2641 PgSqlHostContextPtr ctx = get_context.ctx_;
2642
2643 // If operating in read-only mode, throw exception.
2644 impl_->checkReadOnly(ctx);
2645
2646 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2647 bind_array->add(subnet_id);
2648
2649 // v4
2650 if (addr.isV4()) {
2651 bind_array->add(addr);
2652 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
2653 bind_array));
2654 }
2655
2656 // v6
2657 bind_array->add(addr.toText());
2658
2659 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR6,
2660 bind_array));
2661 }
2662
2663 bool
del4(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len)2664 PgSqlHostDataSource::del4(const SubnetID& subnet_id,
2665 const Host::IdentifierType& identifier_type,
2666 const uint8_t* identifier_begin,
2667 const size_t identifier_len) {
2668 // Get a context
2669 PgSqlHostContextAlloc get_context(*impl_);
2670 PgSqlHostContextPtr ctx = get_context.ctx_;
2671
2672 // If operating in read-only mode, throw exception.
2673 impl_->checkReadOnly(ctx);
2674
2675 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2676
2677 // Subnet-id
2678 bind_array->add(subnet_id);
2679
2680 // identifier-type
2681 bind_array->add(static_cast<uint8_t>(identifier_type));
2682
2683 // identifier
2684 bind_array->add(identifier_begin, identifier_len);
2685
2686 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
2687 bind_array));
2688 }
2689
2690 bool
del6(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len)2691 PgSqlHostDataSource::del6(const SubnetID& subnet_id,
2692 const Host::IdentifierType& identifier_type,
2693 const uint8_t* identifier_begin,
2694 const size_t identifier_len) {
2695 // Get a context
2696 PgSqlHostContextAlloc get_context(*impl_);
2697 PgSqlHostContextPtr ctx = get_context.ctx_;
2698
2699 // If operating in read-only mode, throw exception.
2700 impl_->checkReadOnly(ctx);
2701
2702 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2703
2704 // Subnet-id
2705 bind_array->add(subnet_id);
2706
2707 // identifier-type
2708 bind_array->add(static_cast<uint8_t>(identifier_type));
2709
2710 // identifier
2711 bind_array->add(identifier_begin, identifier_len);
2712
2713 return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
2714 bind_array));
2715 }
2716
2717 ConstHostCollection
getAll(const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len) const2718 PgSqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
2719 const uint8_t* identifier_begin,
2720 const size_t identifier_len) const {
2721 // Get a context
2722 PgSqlHostContextAlloc get_context(*impl_);
2723 PgSqlHostContextPtr ctx = get_context.ctx_;
2724
2725 // Set up the WHERE clause value
2726 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2727
2728 // Identifier value.
2729 bind_array->add(identifier_begin, identifier_len);
2730
2731 // Identifier type.
2732 bind_array->add(static_cast<uint8_t>(identifier_type));
2733
2734 ConstHostCollection result;
2735 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
2736 bind_array, ctx->host_ipv46_exchange_, result, false);
2737
2738 return (result);
2739 }
2740
2741 ConstHostCollection
getAll4(const SubnetID & subnet_id) const2742 PgSqlHostDataSource::getAll4(const SubnetID& subnet_id) const {
2743 // Get a context
2744 PgSqlHostContextAlloc get_context(*impl_);
2745 PgSqlHostContextPtr ctx = get_context.ctx_;
2746
2747 // Set up the WHERE clause value
2748 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2749
2750 // Add the subnet id.
2751 bind_array->add(subnet_id);
2752
2753 ConstHostCollection result;
2754 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4,
2755 bind_array, ctx->host_ipv4_exchange_, result, false);
2756
2757 return (result);
2758 }
2759
2760 ConstHostCollection
getAll6(const SubnetID & subnet_id) const2761 PgSqlHostDataSource::getAll6(const SubnetID& subnet_id) const {
2762 // Get a context
2763 PgSqlHostContextAlloc get_context(*impl_);
2764 PgSqlHostContextPtr ctx = get_context.ctx_;
2765
2766 // Set up the WHERE clause value
2767 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2768
2769 // Add the subnet id.
2770 bind_array->add(subnet_id);
2771
2772 ConstHostCollection result;
2773 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6,
2774 bind_array, ctx->host_ipv6_exchange_, result, false);
2775
2776 return (result);
2777 }
2778
2779 ConstHostCollection
getAllbyHostname(const std::string & hostname) const2780 PgSqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
2781 // Get a context
2782 PgSqlHostContextAlloc get_context(*impl_);
2783 PgSqlHostContextPtr ctx = get_context.ctx_;
2784
2785 // Set up the WHERE clause value
2786 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2787
2788 // Add the hostname.
2789 bind_array->add(hostname);
2790
2791 ConstHostCollection result;
2792 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME,
2793 bind_array, ctx->host_ipv46_exchange_, result, false);
2794
2795 return (result);
2796 }
2797
2798 ConstHostCollection
getAllbyHostname4(const std::string & hostname,const SubnetID & subnet_id) const2799 PgSqlHostDataSource::getAllbyHostname4(const std::string& hostname,
2800 const SubnetID& subnet_id) const {
2801 // Get a context
2802 PgSqlHostContextAlloc get_context(*impl_);
2803 PgSqlHostContextPtr ctx = get_context.ctx_;
2804
2805 // Set up the WHERE clause value
2806 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2807
2808 // Add the hostname.
2809 bind_array->add(hostname);
2810
2811 // Add the subnet id.
2812 bind_array->add(subnet_id);
2813
2814 ConstHostCollection result;
2815 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
2816 bind_array, ctx->host_ipv4_exchange_, result, false);
2817
2818 return (result);
2819 }
2820
2821 ConstHostCollection
getAllbyHostname6(const std::string & hostname,const SubnetID & subnet_id) const2822 PgSqlHostDataSource::getAllbyHostname6(const std::string& hostname,
2823 const SubnetID& subnet_id) const {
2824 // Get a context
2825 PgSqlHostContextAlloc get_context(*impl_);
2826 PgSqlHostContextPtr ctx = get_context.ctx_;
2827
2828 // Set up the WHERE clause value
2829 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2830
2831 // Add the hostname.
2832 bind_array->add(hostname);
2833
2834 // Add the subnet id.
2835 bind_array->add(subnet_id);
2836
2837 ConstHostCollection result;
2838 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
2839 bind_array, ctx->host_ipv6_exchange_, result, false);
2840
2841 return (result);
2842 }
2843
2844 ConstHostCollection
getPage4(const SubnetID & subnet_id,size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2845 PgSqlHostDataSource::getPage4(const SubnetID& subnet_id,
2846 size_t& /*source_index*/,
2847 uint64_t lower_host_id,
2848 const HostPageSize& page_size) const {
2849 // Get a context
2850 PgSqlHostContextAlloc get_context(*impl_);
2851 PgSqlHostContextPtr ctx = get_context.ctx_;
2852
2853 // Set up the WHERE clause value
2854 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2855
2856 // Add the subnet id.
2857 bind_array->add(subnet_id);
2858
2859 // Add the lower bound host id.
2860 bind_array->add(lower_host_id);
2861
2862 // Add the page size value.
2863 string page_size_data =
2864 boost::lexical_cast<std::string>(page_size.page_size_);
2865 bind_array->add(page_size_data);
2866
2867 ConstHostCollection result;
2868 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
2869 bind_array, ctx->host_ipv4_exchange_, result, false);
2870
2871 return (result);
2872 }
2873
2874 ConstHostCollection
getPage6(const SubnetID & subnet_id,size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2875 PgSqlHostDataSource::getPage6(const SubnetID& subnet_id,
2876 size_t& /*source_index*/,
2877 uint64_t lower_host_id,
2878 const HostPageSize& page_size) const {
2879 // Get a context
2880 PgSqlHostContextAlloc get_context(*impl_);
2881 PgSqlHostContextPtr ctx = get_context.ctx_;
2882
2883 // Set up the WHERE clause value
2884 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2885
2886 // Add the subnet id.
2887 bind_array->add(subnet_id);
2888
2889 // Add the lower bound host id.
2890 bind_array->add(lower_host_id);
2891
2892 // Add the page size value.
2893 string page_size_data =
2894 boost::lexical_cast<std::string>(page_size.page_size_);
2895 bind_array->add(page_size_data);
2896
2897 ConstHostCollection result;
2898 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
2899 bind_array, ctx->host_ipv6_exchange_, result, false);
2900
2901 return (result);
2902 }
2903
2904 ConstHostCollection
getPage4(size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2905 PgSqlHostDataSource::getPage4(size_t& /*source_index*/,
2906 uint64_t lower_host_id,
2907 const HostPageSize& page_size) const {
2908 // Get a context
2909 PgSqlHostContextAlloc get_context(*impl_);
2910 PgSqlHostContextPtr ctx = get_context.ctx_;
2911
2912 // Set up the WHERE clause value
2913 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2914
2915 // Add the lower bound host id.
2916 bind_array->add(lower_host_id);
2917
2918 // Add the page size value.
2919 string page_size_data =
2920 boost::lexical_cast<std::string>(page_size.page_size_);
2921 bind_array->add(page_size_data);
2922
2923 ConstHostCollection result;
2924 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE4,
2925 bind_array, ctx->host_ipv4_exchange_, result, false);
2926
2927 return (result);
2928 }
2929
2930 ConstHostCollection
getPage6(size_t &,uint64_t lower_host_id,const HostPageSize & page_size) const2931 PgSqlHostDataSource::getPage6(size_t& /*source_index*/,
2932 uint64_t lower_host_id,
2933 const HostPageSize& page_size) const {
2934 // Get a context
2935 PgSqlHostContextAlloc get_context(*impl_);
2936 PgSqlHostContextPtr ctx = get_context.ctx_;
2937
2938 // Set up the WHERE clause value
2939 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2940
2941 // Add the lower bound host id.
2942 bind_array->add(lower_host_id);
2943
2944 // Add the page size value.
2945 string page_size_data =
2946 boost::lexical_cast<std::string>(page_size.page_size_);
2947 bind_array->add(page_size_data);
2948
2949 ConstHostCollection result;
2950 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE6,
2951 bind_array, ctx->host_ipv6_exchange_, result, false);
2952
2953 return (result);
2954 }
2955
2956 ConstHostCollection
getAll4(const asiolink::IOAddress & address) const2957 PgSqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
2958 // Get a context
2959 PgSqlHostContextAlloc get_context(*impl_);
2960 PgSqlHostContextPtr ctx = get_context.ctx_;
2961
2962 // Set up the WHERE clause value
2963 PsqlBindArrayPtr bind_array(new PsqlBindArray());
2964
2965 // v4 Reservation address
2966 bind_array->add(address);
2967
2968 ConstHostCollection result;
2969 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_ADDR,
2970 bind_array, ctx->host_ipv4_exchange_, result, false);
2971
2972 return (result);
2973 }
2974
2975 ConstHostPtr
get4(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len) const2976 PgSqlHostDataSource::get4(const SubnetID& subnet_id,
2977 const Host::IdentifierType& identifier_type,
2978 const uint8_t* identifier_begin,
2979 const size_t identifier_len) const {
2980 // Get a context
2981 PgSqlHostContextAlloc get_context(*impl_);
2982 PgSqlHostContextPtr ctx = get_context.ctx_;
2983
2984 return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
2985 PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
2986 ctx->host_ipv4_exchange_));
2987 }
2988
2989 ConstHostPtr
get4(const SubnetID & subnet_id,const asiolink::IOAddress & address) const2990 PgSqlHostDataSource::get4(const SubnetID& subnet_id,
2991 const asiolink::IOAddress& address) const {
2992 // Get a context
2993 PgSqlHostContextAlloc get_context(*impl_);
2994 PgSqlHostContextPtr ctx = get_context.ctx_;
2995
2996 if (!address.isV4()) {
2997 isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
2998 " wrong address type, address supplied is an IPv6 address");
2999 }
3000
3001 // Set up the WHERE clause value
3002 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3003
3004 // Add the subnet id
3005 bind_array->add(subnet_id);
3006
3007 // Add the address
3008 bind_array->add(address);
3009
3010 ConstHostCollection collection;
3011 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3012 bind_array, ctx->host_ipv4_exchange_, collection, true);
3013
3014 // Return single record if present, else clear the host.
3015 ConstHostPtr result;
3016 if (!collection.empty()) {
3017 result = *collection.begin();
3018 }
3019
3020 return (result);
3021 }
3022
3023 ConstHostCollection
getAll4(const SubnetID & subnet_id,const asiolink::IOAddress & address) const3024 PgSqlHostDataSource::getAll4(const SubnetID& subnet_id,
3025 const asiolink::IOAddress& address) const {
3026 // Get a context
3027 PgSqlHostContextAlloc get_context(*impl_);
3028 PgSqlHostContextPtr ctx = get_context.ctx_;
3029
3030 if (!address.isV4()) {
3031 isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
3032 " wrong address type, address supplied is an IPv6 address");
3033 }
3034
3035 // Set up the WHERE clause value
3036 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3037
3038 // Add the subnet id
3039 bind_array->add(subnet_id);
3040
3041 // Add the address
3042 bind_array->add(address);
3043
3044 ConstHostCollection collection;
3045 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
3046 bind_array, ctx->host_ipv4_exchange_, collection, false);
3047 return (collection);
3048 }
3049
3050 ConstHostPtr
get6(const SubnetID & subnet_id,const Host::IdentifierType & identifier_type,const uint8_t * identifier_begin,const size_t identifier_len) const3051 PgSqlHostDataSource::get6(const SubnetID& subnet_id,
3052 const Host::IdentifierType& identifier_type,
3053 const uint8_t* identifier_begin,
3054 const size_t identifier_len) const {
3055 // Get a context
3056 PgSqlHostContextAlloc get_context(*impl_);
3057 PgSqlHostContextPtr ctx = get_context.ctx_;
3058
3059 return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
3060 PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
3061 ctx->host_ipv6_exchange_));
3062 }
3063
3064 ConstHostPtr
get6(const asiolink::IOAddress & prefix,const uint8_t prefix_len) const3065 PgSqlHostDataSource::get6(const asiolink::IOAddress& prefix,
3066 const uint8_t prefix_len) const {
3067 if (!prefix.isV6()) {
3068 isc_throw(BadValue, "PgSqlHostDataSource::get6(prefix, prefix_len): "
3069 "wrong address type, address supplied is an IPv4 address");
3070 }
3071
3072 // Get a context
3073 PgSqlHostContextAlloc get_context(*impl_);
3074 PgSqlHostContextPtr ctx = get_context.ctx_;
3075
3076 // Set up the WHERE clause value
3077 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3078
3079 // Add the prefix
3080 bind_array->add(prefix);
3081
3082 // Add the prefix length
3083 bind_array->add(prefix_len);
3084
3085 ConstHostCollection collection;
3086 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
3087 bind_array, ctx->host_ipv6_exchange_, collection, true);
3088
3089 // Return single record if present, else clear the host.
3090 ConstHostPtr result;
3091 if (!collection.empty()) {
3092 result = *collection.begin();
3093 }
3094
3095 return (result);
3096 }
3097
3098 ConstHostPtr
get6(const SubnetID & subnet_id,const asiolink::IOAddress & address) const3099 PgSqlHostDataSource::get6(const SubnetID& subnet_id,
3100 const asiolink::IOAddress& address) const {
3101 if (!address.isV6()) {
3102 isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3103 "wrong address type, address supplied is an IPv4 address");
3104 }
3105
3106 // Get a context
3107 PgSqlHostContextAlloc get_context(*impl_);
3108 PgSqlHostContextPtr ctx = get_context.ctx_;
3109
3110 // Set up the WHERE clause value
3111 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3112
3113 // Add the subnet id
3114 bind_array->add(subnet_id);
3115
3116 // Add the prefix
3117 bind_array->add(address);
3118
3119 ConstHostCollection collection;
3120 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3121 bind_array, ctx->host_ipv6_exchange_, collection, true);
3122
3123 // Return single record if present, else clear the host.
3124 ConstHostPtr result;
3125 if (!collection.empty()) {
3126 result = *collection.begin();
3127 }
3128
3129 return (result);
3130 }
3131
3132 ConstHostCollection
getAll6(const SubnetID & subnet_id,const asiolink::IOAddress & address) const3133 PgSqlHostDataSource::getAll6(const SubnetID& subnet_id,
3134 const asiolink::IOAddress& address) const {
3135 if (!address.isV6()) {
3136 isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
3137 "wrong address type, address supplied is an IPv4 address");
3138 }
3139
3140 // Get a context
3141 PgSqlHostContextAlloc get_context(*impl_);
3142 PgSqlHostContextPtr ctx = get_context.ctx_;
3143
3144 // Set up the WHERE clause value
3145 PsqlBindArrayPtr bind_array(new PsqlBindArray());
3146
3147 // Add the subnet id
3148 bind_array->add(subnet_id);
3149
3150 // Add the prefix
3151 bind_array->add(address);
3152
3153 ConstHostCollection collection;
3154 impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
3155 bind_array, ctx->host_ipv6_exchange_, collection, false);
3156 return (collection);
3157 }
3158
3159
3160 // Miscellaneous database methods.
3161
3162 std::string
getName() const3163 PgSqlHostDataSource::getName() const {
3164 std::string name = "";
3165 // Get a context
3166 PgSqlHostContextAlloc get_context(*impl_);
3167 PgSqlHostContextPtr ctx = get_context.ctx_;
3168
3169 try {
3170 name = ctx->conn_.getParameter("name");
3171 } catch (...) {
3172 // Return an empty name
3173 }
3174 return (name);
3175 }
3176
3177 std::string
getDescription() const3178 PgSqlHostDataSource::getDescription() const {
3179 return (std::string("Host data source that stores host information"
3180 "in PostgreSQL database"));
3181 }
3182
3183 std::pair<uint32_t, uint32_t>
getVersion() const3184 PgSqlHostDataSource::getVersion() const {
3185 return(impl_->getVersion());
3186 }
3187
3188 void
commit()3189 PgSqlHostDataSource::commit() {
3190 // Get a context
3191 PgSqlHostContextAlloc get_context(*impl_);
3192 PgSqlHostContextPtr ctx = get_context.ctx_;
3193
3194 // If operating in read-only mode, throw exception.
3195 impl_->checkReadOnly(ctx);
3196 ctx->conn_.commit();
3197 }
3198
3199 void
rollback()3200 PgSqlHostDataSource::rollback() {
3201 // Get a context
3202 PgSqlHostContextAlloc get_context(*impl_);
3203 PgSqlHostContextPtr ctx = get_context.ctx_;
3204
3205 // If operating in read-only mode, throw exception.
3206 impl_->checkReadOnly(ctx);
3207 ctx->conn_.rollback();
3208 }
3209
3210 bool
setIPReservationsUnique(const bool unique)3211 PgSqlHostDataSource::setIPReservationsUnique(const bool unique) {
3212 impl_->ip_reservations_unique_ = unique;
3213 return (true);
3214 }
3215
3216 bool
isUnusable()3217 PgSqlHostDataSource::isUnusable() {
3218 return (impl_->unusable_);
3219 }
3220
3221 } // namespace dhcp
3222 } // namespace isc
3223