pgsql_host_data_source.cc 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956
  1. // Copyright (C) 2016 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. #include <config.h>
  7. #include <dhcp/libdhcp++.h>
  8. #include <dhcp/option.h>
  9. #include <dhcp/option_definition.h>
  10. #include <dhcp/option_space.h>
  11. #include <dhcpsrv/db_exceptions.h>
  12. #include <dhcpsrv/cfg_option.h>
  13. #include <dhcpsrv/dhcpsrv_log.h>
  14. #include <dhcpsrv/pgsql_host_data_source.h>
  15. #include <dhcpsrv/db_exceptions.h>
  16. #include <util/buffer.h>
  17. #include <util/optional_value.h>
  18. #include <boost/algorithm/string/split.hpp>
  19. #include <boost/algorithm/string/classification.hpp>
  20. #include <boost/array.hpp>
  21. #include <boost/pointer_cast.hpp>
  22. #include <boost/static_assert.hpp>
  23. #include <stdint.h>
  24. #include <string>
  25. using namespace isc;
  26. using namespace isc::asiolink;
  27. using namespace isc::dhcp;
  28. using namespace isc::util;
  29. using namespace std;
  30. namespace {
  31. /// @brief Maximum length of option value.
  32. /// The maximum size of the raw option data that may be read from the
  33. /// database.
  34. const size_t OPTION_VALUE_MAX_LEN = 4096;
  35. /// @brief Numeric value representing last supported identifier.
  36. ///
  37. /// This value is used to validate whether the identifier type stored in
  38. /// a database is within bounds. of supported identifiers.
  39. const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
  40. /// @brief Maximum length of DHCP identifier value.
  41. const size_t DHCP_IDENTIFIER_MAX_LEN = 128;
  42. /// @brief This class provides mechanisms for sending and retrieving
  43. /// information from the 'hosts' table.
  44. ///
  45. /// This class is used to insert and retrieve entries from the 'hosts' table.
  46. /// The queries used with this class do not retrieve IPv6 reservations or
  47. /// options associated with a host to minimize impact on performance. Other
  48. /// classes derived from @ref PgSqlHostExchange should be used to retrieve
  49. /// information about IPv6 reservations and options.
  50. class PgSqlHostExchange : public PgSqlExchange {
  51. private:
  52. /// @brief Column numbers for each column in the hosts table.
  53. /// These are used for both retrieving data and for looking up
  54. /// column labels for logging. Note that their numeric order
  55. /// MUST match that of the column order in the hosts table.
  56. static const int HOST_ID_COL = 0;
  57. static const int DHCP_IDENTIFIER_COL = 1;
  58. static const int DHCP_IDENTIFIER_TYPE_COL = 2;
  59. static const int DHCP4_SUBNET_ID_COL = 3;
  60. static const int DHCP6_SUBNET_ID_COL = 4;
  61. static const int IPV4_ADDRESS_COL = 5;
  62. static const int HOSTNAME_COL = 6;
  63. static const int DHCP4_CLIENT_CLASSES_COL = 7;
  64. static const int DHCP6_CLIENT_CLASSES_COL = 8;
  65. /// @brief Number of columns returned for SELECT queries send by this class.
  66. static const size_t HOST_COLUMNS = 9;
  67. public:
  68. /// @brief Constructor
  69. ///
  70. /// @param additional_columns_num This value is set by the derived classes
  71. /// to indicate how many additional columns will be returned by SELECT
  72. /// queries performed by the derived class. This constructor will allocate
  73. /// resources for these columns, e.g. binding table, error indicators.
  74. PgSqlHostExchange(const size_t additional_columns_num = 0)
  75. : PgSqlExchange(HOST_COLUMNS + additional_columns_num) {
  76. // Set the column names for use by this class. This only comprises
  77. // names used by the PgSqlHostExchange class. Derived classes will
  78. // need to set names for the columns they use. Currenty these are
  79. // only used for logging purposes.
  80. columns_[HOST_ID_COL] = "host_id";
  81. columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
  82. columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
  83. columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
  84. columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
  85. columns_[IPV4_ADDRESS_COL] = "ipv4_address";
  86. columns_[HOSTNAME_COL] = "hostname";
  87. columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
  88. columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
  89. BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
  90. };
  91. /// @brief Virtual destructor.
  92. virtual ~PgSqlHostExchange() {
  93. }
  94. /// @brief Reinitializes state information
  95. ///
  96. /// This function should be called in between statement executions.
  97. /// Deriving classes should invoke this method as well as be reset
  98. /// all of their own stateful values.
  99. virtual void clear() {
  100. host_.reset();
  101. };
  102. /// @brief Returns index of the first uninitialized column name.
  103. ///
  104. /// This method is called by the derived classes to determine which
  105. /// column indexes are available for the derived classes within a
  106. /// binding array, error array and column names. This method
  107. /// determines the first available index by searching the first
  108. /// empty value within the columns_ vector. Previously we relied on
  109. /// the fixed values set for each class, but this was hard to maintain
  110. /// when new columns were added to the SELECT queries. It required
  111. /// modifying indexes in all derived classes.
  112. ///
  113. /// Derived classes must call this method in their constructors and
  114. /// use returned value as an index for the first column used by the
  115. /// derived class and increment this value for each subsequent column.
  116. size_t findAvailColumn() const {
  117. std::vector<std::string>::const_iterator empty_column =
  118. std::find(columns_.begin(), columns_.end(), std::string());
  119. return (std::distance(columns_.begin(), empty_column));
  120. }
  121. /// @brief Returns value of host id in the given row.
  122. ///
  123. /// This method is used to "look ahead" at the host_id in a row
  124. /// without having to call retrieveHost()
  125. HostID getHostId(const PgSqlResult& r, int row) {
  126. HostID host_id;
  127. getColumnValue(r, row, HOST_ID_COL, host_id);
  128. return (host_id);
  129. }
  130. /// @brief Populate a bind array from a host
  131. ///
  132. /// Constructs a PsqlBindArray for sending data stored in a Host object
  133. /// to the database.
  134. ///
  135. /// @param host Host object to be added to the database.
  136. /// None of the fields in the host reservation are modified -
  137. /// the host data is only read.
  138. ///
  139. /// @return pointer to newly constructed bind_array containing the
  140. /// bound values extracted from host
  141. ///
  142. /// @throw DbOperationError if bind_array cannot be populated.
  143. PsqlBindArrayPtr
  144. createBindForSend(const HostPtr& host) {
  145. if (!host) {
  146. isc_throw(BadValue, "createBindForSend:: host object is NULL");
  147. }
  148. // Store the host to ensure bound values remain in scope
  149. host_ = host;
  150. // Bind the host data to the array
  151. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  152. try {
  153. // host_id : is auto_incremented skip it
  154. // dhcp_identifier : BYTEA NOT NULL
  155. bind_array->add(host->getIdentifier());
  156. // dhcp_identifier_type : SMALLINT NOT NULL
  157. bind_array->add(host->getIdentifierType());
  158. // dhcp4_subnet_id : INT NULL
  159. bind_array->add(host->getIPv4SubnetID());
  160. // dhcp6_subnet_id : INT NULL
  161. bind_array->add(host->getIPv6SubnetID());
  162. // ipv4_address : BIGINT NULL
  163. bind_array->add(host->getIPv4Reservation());
  164. // hostname : VARCHAR(255) NULL
  165. bind_array->add(host->getHostname());
  166. // dhcp4_client_classes : VARCHAR(255) NULL
  167. // Override default separator to not include space after comma.
  168. bind_array->addTempString(host->getClientClasses4().toText(","));
  169. // dhcp6_client_classes : VARCHAR(255) NULL
  170. bind_array->addTempString(host->getClientClasses6().toText(","));
  171. } catch (const std::exception& ex) {
  172. host_.reset();
  173. isc_throw(DbOperationError,
  174. "Could not create bind array from Host: "
  175. << host->getHostname() << ", reason: " << ex.what());
  176. }
  177. return (bind_array);
  178. };
  179. /// @brief Processes one row of data fetched from a database.
  180. ///
  181. /// The processed data must contain host id, which uniquely identifies a
  182. /// host. This method creates a host and inserts it to the hosts collection
  183. /// only if the last inserted host has a different host id. This prevents
  184. /// adding duplicated hosts to the collection, assuming that processed
  185. /// rows are primarily ordered by host id column.
  186. ///
  187. /// This method must be overriden in the derived classes to also
  188. /// retrieve IPv6 reservations and DHCP options associated with a host.
  189. ///
  190. /// @param [out] hosts Collection of hosts to which a new host created
  191. /// from the processed data should be inserted.
  192. virtual void processRowData(ConstHostCollection& hosts,
  193. const PgSqlResult& r, int row) {
  194. // Peek at the host id , so we can skip it if we already have it
  195. // This lets us avoid constructing a copy of host for each
  196. // of its sub-rows (options, etc...)
  197. HostID row_host_id = getHostId(r, row);
  198. // Add new host only if there are no hosts or the host id of the
  199. // most recently added host is different than the host id of the
  200. // currently processed host.
  201. if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
  202. HostPtr host = retrieveHost(r, row, row_host_id);
  203. hosts.push_back(host);
  204. }
  205. }
  206. /// @brief Creates a Host object from a given row in a result set.
  207. ///
  208. /// @param r result set containing one or more rows from the hosts table
  209. /// @param row index within the result set of the row to process
  210. /// @param peeked_host_id if the caller has peeked ahead at the row's
  211. /// host_id, it can be passed in here to avoid fetching it from the row
  212. /// a second time.
  213. ///
  214. /// @return HostPtr to the newly created Host object
  215. /// @throw DbOperationError if the host cannot be created.
  216. HostPtr retrieveHost(const PgSqlResult& r, int row,
  217. const HostID& peeked_host_id = 0) {
  218. // If the caller peeked ahead at the host_id use that, otherwise
  219. // read it from the row.
  220. HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
  221. // dhcp_identifier : BYTEA NOT NULL
  222. uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
  223. size_t identifier_len;
  224. convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
  225. sizeof(identifier_value), identifier_len);
  226. // dhcp_identifier_type : SMALLINT NOT NULL
  227. uint8_t type;
  228. getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
  229. if (type > MAX_IDENTIFIER_TYPE) {
  230. isc_throw(BadValue, "invalid dhcp identifier type returned: "
  231. << static_cast<int>(type));
  232. }
  233. Host::IdentifierType identifier_type =
  234. static_cast<Host::IdentifierType>(type);
  235. // dhcp4_subnet_id : INT NULL
  236. uint32_t subnet_id;
  237. getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
  238. SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
  239. // dhcp6_subnet_id : INT NULL
  240. getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
  241. SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
  242. // ipv4_address : BIGINT NULL
  243. uint32_t addr4;
  244. getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
  245. isc::asiolink::IOAddress ipv4_reservation(addr4);
  246. // hostname : VARCHAR(255) NULL
  247. std::string hostname;
  248. getColumnValue(r, row, HOSTNAME_COL, hostname);
  249. // dhcp4_client_classes : VARCHAR(255) NULL
  250. std::string dhcp4_client_classes;
  251. getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
  252. // dhcp6_client_classes : VARCHAR(255) NULL
  253. std::string dhcp6_client_classes;
  254. getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
  255. // Finally, attempt to create the new host.
  256. HostPtr host;
  257. try {
  258. host.reset(new Host(identifier_value, identifier_len,
  259. identifier_type, dhcp4_subnet_id,
  260. dhcp6_subnet_id, ipv4_reservation, hostname,
  261. dhcp4_client_classes, dhcp6_client_classes));
  262. host->setHostId(host_id);
  263. } catch (const isc::Exception& ex) {
  264. isc_throw(DbOperationError, "Could not create host: " << ex.what());
  265. }
  266. return(host);
  267. };
  268. protected:
  269. /// Pointer to Host object holding information to be inserted into
  270. /// Hosts table. This is used to retain scope.
  271. HostPtr host_;
  272. };
  273. /// @brief Extends base exchange class with ability to retrieve DHCP options
  274. /// from the 'dhcp4_options' and 'dhcp6_options' tables.
  275. ///
  276. /// This class provides means to retrieve both DHCPv4 and DHCPv6 options
  277. /// along with the host information. It is not used to retrieve IPv6
  278. /// reservations. The following types of queries are supported:
  279. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
  280. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
  281. /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
  282. class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
  283. private:
  284. /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
  285. static const size_t OPTION_COLUMNS = 6;
  286. /// @brief Receives DHCPv4 or DHCPv6 options information from the
  287. /// dhcp4_options or dhcp6_options tables respectively.
  288. ///
  289. /// The PgSqlHostWithOptionsExchange class holds two respective instances
  290. /// of this class, one for receiving DHCPv4 options, one for receiving
  291. /// DHCPv6 options.
  292. ///
  293. /// The following are the basic functions of this class:
  294. /// - bind class members to specific columns in MySQL binding tables,
  295. /// - set DHCP options specific column names,
  296. /// - create instances of options retrieved from the database.
  297. ///
  298. /// The reason for isolating those functions in a separate C++ class is
  299. /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
  300. class OptionProcessor {
  301. public:
  302. /// @brief Constructor.
  303. ///
  304. /// @param universe V4 or V6. The type of the options' instances
  305. /// created by this class depends on this parameter.
  306. /// @param start_column Index of the first column to be used by this
  307. /// class.
  308. OptionProcessor(const Option::Universe& universe,
  309. const size_t start_column)
  310. : universe_(universe), start_column_(start_column),
  311. option_id_index_(start_column), code_index_(start_column_ + 1),
  312. value_index_(start_column_ + 2),
  313. formatted_value_index_(start_column_ + 3),
  314. space_index_(start_column_ + 4),
  315. persistent_index_(start_column_ + 5),
  316. most_recent_option_id_(0) {
  317. }
  318. /// @brief Reintializes state information
  319. ///
  320. /// This function should be called prior to processing a fetched
  321. /// set of options.
  322. void clear() {
  323. most_recent_option_id_ = 0;
  324. }
  325. /// @brief Creates instance of the currently processed option.
  326. ///
  327. /// This method detects if the currently processed option is a new
  328. /// instance. It makes its determination by comparing the identifier
  329. /// of the currently processed option, with the most recently processed
  330. /// option. If the current value is greater than the id of the recently
  331. /// processed option it is assumed that the processed row holds new
  332. /// option information. In such case the option instance is created and
  333. /// inserted into the configuration passed as argument.
  334. ///
  335. /// This logic is necessary to deal with result sets made from multiple
  336. /// left joins which contain duplicated data. For instance queries
  337. /// returning both v4 and v6 options for a host would generate result
  338. /// sets similar to this:
  339. /// @code
  340. ///
  341. /// row 0: host-1 v4-opt-1 v6-opt-1
  342. /// row 1: host-1 v4-opt-1 v6-opt-2
  343. /// row 2: host-1 v4-opt-1 v6-opt-3
  344. /// row 4: host-1 v4-opt-2 v6-opt-1
  345. /// row 5: host-1 v4-opt-2 v6-opt-2
  346. /// row 6: host-1 v4-opt-2 v6-opt-3
  347. /// row 7: host-2 v4-opt-1 v6-opt-1
  348. /// row 8: host-2 v4-opt-2 v6-opt-1
  349. /// :
  350. /// @endcode
  351. ///
  352. /// @param cfg Pointer to the configuration object into which new
  353. /// option instances should be inserted.
  354. /// @param r result set containing one or more rows from a dhcp
  355. /// options table.
  356. /// @param row index within the result set of the row to process
  357. void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
  358. int row) {
  359. // If the option id on this row is NULL, then there's no
  360. // option of this type (4/6) on this row to fetch, so bail.
  361. if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
  362. return;
  363. }
  364. // option_id: INT
  365. uint64_t option_id;
  366. PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);
  367. // The row option id must be greater than id if the most recent
  368. // option because they are ordered by option id. Otherwise
  369. // we assume that we have already processed this option.
  370. if (most_recent_option_id_ >= option_id) {
  371. return;
  372. }
  373. // Remember current option id as the most recent processed one. We
  374. // will be comparing it with option ids in subsequent rows.
  375. most_recent_option_id_ = option_id;
  376. // code: SMALLINT NOT NULL
  377. uint16_t code;
  378. PgSqlExchange::getColumnValue(r, row, code_index_, code);
  379. // value: BYTEA
  380. uint8_t value[OPTION_VALUE_MAX_LEN];
  381. size_t value_len;
  382. PgSqlExchange::convertFromBytea(r, row, value_index_, value,
  383. sizeof(value), value_len);
  384. // formatted_value: TEXT
  385. std::string formatted_value;
  386. PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
  387. formatted_value);
  388. // space: VARCHAR(128)
  389. std::string space;
  390. PgSqlExchange::getColumnValue(r, row, space_index_, space);
  391. // persistent: BOOL default false
  392. bool persistent;
  393. PgSqlExchange::getColumnValue(r, row, persistent_index_,
  394. persistent);
  395. // Options are held in a binary or textual format in the database.
  396. // This is similar to having an option specified in a server
  397. // configuration file. Such option is converted to appropriate C++
  398. // class, using option definition. Thus, we need to find the
  399. // option definition for this option code and option space.
  400. // If the option space is a standard DHCPv4 or DHCPv6 option space,
  401. // this is most likely a standard option, for which we have a
  402. // definition created within libdhcp++.
  403. OptionDefinitionPtr def;
  404. if ((space == DHCP4_OPTION_SPACE) ||
  405. (space == DHCP6_OPTION_SPACE)) {
  406. def = LibDHCP::getOptionDef(universe_, code);
  407. }
  408. // Otherwise, we may check if this an option encapsulated within the
  409. // vendor space.
  410. if (!def && (space != DHCP4_OPTION_SPACE) &&
  411. (space != DHCP6_OPTION_SPACE)) {
  412. uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
  413. if (vendor_id > 0) {
  414. def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
  415. code);
  416. }
  417. }
  418. // In all other cases, we use runtime option definitions, which
  419. // should be also registered within the libdhcp++.
  420. if (!def) {
  421. def = LibDHCP::getRuntimeOptionDef(space, code);
  422. }
  423. OptionPtr option;
  424. if (!def) {
  425. // If no definition found, we use generic option type.
  426. OptionBuffer buf(value, value + value_len);
  427. option.reset(new Option(universe_, code, buf.begin(),
  428. buf.end()));
  429. } else {
  430. // The option value may be specified in textual or binary format
  431. // in the database. If formatted_value is empty, the binary
  432. // format is used. Depending on the format we use a different
  433. // variant of the optionFactory function.
  434. if (formatted_value.empty()) {
  435. OptionBuffer buf(value, value + value_len);
  436. option = def->optionFactory(universe_, code, buf.begin(),
  437. buf.end());
  438. } else {
  439. // Spit the value specified in comma separated values
  440. // format.
  441. std::vector<std::string> split_vec;
  442. boost::split(split_vec, formatted_value,
  443. boost::is_any_of(","));
  444. option = def->optionFactory(universe_, code, split_vec);
  445. }
  446. }
  447. OptionDescriptor desc(option, persistent, formatted_value);
  448. cfg->add(desc, space);
  449. }
  450. /// @brief Specify column names.
  451. ///
  452. /// @param [out] columns Reference to a vector holding names of option
  453. /// specific columns.
  454. void setColumnNames(std::vector<std::string>& columns) {
  455. columns[option_id_index_] = "option_id";
  456. columns[code_index_] = "code";
  457. columns[value_index_] = "value";
  458. columns[formatted_value_index_] = "formatted_value";
  459. columns[space_index_] = "space";
  460. columns[persistent_index_] = "persistent";
  461. }
  462. private:
  463. /// @brief Universe: V4 or V6.
  464. Option::Universe universe_;
  465. /// @brief Index of first column used by this class.
  466. size_t start_column_;
  467. //@}
  468. /// @name Indexes of the specific columns
  469. //@{
  470. /// @brief Option id
  471. size_t option_id_index_;
  472. /// @brief Code
  473. size_t code_index_;
  474. /// @brief Value
  475. size_t value_index_;
  476. /// @brief Formatted value
  477. size_t formatted_value_index_;
  478. /// @brief Space
  479. size_t space_index_;
  480. /// @brief Persistent
  481. size_t persistent_index_;
  482. //@}
  483. /// @brief Option id for last processed row.
  484. uint64_t most_recent_option_id_;
  485. };
  486. /// @brief Pointer to the @ref OptionProcessor class.
  487. typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
  488. public:
  489. /// @brief DHCP option types to be fetched from the database.
  490. ///
  491. /// Supported types are:
  492. /// - Only DHCPv4 options,
  493. /// - Only DHCPv6 options,
  494. /// - Both DHCPv4 and DHCPv6 options.
  495. enum FetchedOptions {
  496. DHCP4_ONLY,
  497. DHCP6_ONLY,
  498. DHCP4_AND_DHCP6
  499. };
  500. /// @brief Constructor.
  501. ///
  502. /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
  503. /// be fetched from the database for a host.
  504. /// @param additional_columns_num Number of additional columns for which
  505. /// resources should be allocated, e.g. binding table, column names etc.
  506. /// This parameter should be set to a non zero value by derived classes to
  507. /// allocate resources for the columns supported by derived classes.
  508. PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
  509. const size_t additional_columns_num = 0)
  510. : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
  511. + additional_columns_num),
  512. opt_proc4_(), opt_proc6_() {
  513. // Create option processor for DHCPv4 options, if required.
  514. if ((fetched_options == DHCP4_ONLY) ||
  515. (fetched_options == DHCP4_AND_DHCP6)) {
  516. opt_proc4_.reset(new OptionProcessor(Option::V4,
  517. findAvailColumn()));
  518. opt_proc4_->setColumnNames(columns_);
  519. }
  520. // Create option processor for DHCPv6 options, if required.
  521. if ((fetched_options == DHCP6_ONLY) ||
  522. (fetched_options == DHCP4_AND_DHCP6)) {
  523. opt_proc6_.reset(new OptionProcessor(Option::V6,
  524. findAvailColumn()));
  525. opt_proc6_->setColumnNames(columns_);
  526. }
  527. }
  528. /// @brief Clears state information
  529. ///
  530. /// This function should be called in between statement executions.
  531. /// Deriving classes should invoke this method as well as be reset
  532. /// all of their own stateful values.
  533. virtual void clear() {
  534. PgSqlHostExchange::clear();
  535. if (opt_proc4_) {
  536. opt_proc4_->clear();
  537. }
  538. if (opt_proc6_) {
  539. opt_proc6_->clear();
  540. }
  541. }
  542. /// @brief Processes the current row.
  543. ///
  544. /// The fetched row includes both host information and DHCP option
  545. /// information. Because the SELECT queries use one or more LEFT JOIN
  546. /// clauses, the result set may contain duplicated host or options
  547. /// entries. This method detects duplicated information and discards such
  548. /// entries.
  549. ///
  550. /// @param [out] hosts Container holding parsed hosts and options.
  551. virtual void processRowData(ConstHostCollection& hosts,
  552. const PgSqlResult& r, int row) {
  553. HostPtr current_host;
  554. if (hosts.empty()) {
  555. // Must be the first one, fetch it.
  556. current_host = retrieveHost(r, row);
  557. hosts.push_back(current_host);
  558. } else {
  559. // Peek at the host id so we can skip it if we already have
  560. // this host. This lets us avoid retrieving the host needlessly
  561. // for each of its sub-rows (options, etc...).
  562. HostID row_host_id = getHostId(r, row);
  563. current_host = boost::const_pointer_cast<Host>(hosts.back());
  564. // if the row's host id is greater than the one we've been
  565. // working on we're starting a new host, so fetch it.
  566. if (row_host_id > current_host->getHostId()) {
  567. current_host = retrieveHost(r, row, row_host_id);
  568. hosts.push_back(current_host);
  569. }
  570. }
  571. // Parse DHCPv4 options if required to do so.
  572. if (opt_proc4_) {
  573. CfgOptionPtr cfg = current_host->getCfgOption4();
  574. opt_proc4_->retrieveOption(cfg, r, row);
  575. }
  576. // Parse DHCPv6 options if required to do so.
  577. if (opt_proc6_) {
  578. CfgOptionPtr cfg = current_host->getCfgOption6();
  579. opt_proc6_->retrieveOption(cfg, r, row);
  580. }
  581. }
  582. private:
  583. /// @brief Returns a number of columns required to retrieve option data.
  584. ///
  585. /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
  586. /// DHCPv6 a different number of columns is required in the binding array.
  587. /// This method returns the number of required columns, according to the
  588. /// value of @c fetched_columns passed in the constructor.
  589. ///
  590. /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
  591. /// both types of options should be retrieved.
  592. ///
  593. /// @return Number of required columns.
  594. static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
  595. return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
  596. OPTION_COLUMNS);
  597. }
  598. /// @brief Pointer to DHCPv4 options processor.
  599. ///
  600. /// If this object is NULL, the DHCPv4 options are not fetched.
  601. OptionProcessorPtr opt_proc4_;
  602. /// @brief Pointer to DHCPv6 options processor.
  603. ///
  604. /// If this object is NULL, the DHCPv6 options are not fetched.
  605. OptionProcessorPtr opt_proc6_;
  606. };
  607. /// @brief This class provides mechanisms for sending and retrieving
  608. /// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
  609. ///
  610. /// This class extends the @ref PgSqlHostWithOptionsExchange class with the
  611. /// mechanisms to retrieve IPv6 reservations. This class is used in situations
  612. /// when it is desired to retrieve DHCPv6 specific information about the host
  613. /// (DHCPv6 options and reservations), or entire information about the host
  614. /// (DHCPv4 options, DHCPv6 options and reservations). The following are the
  615. /// queries used with this class:
  616. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
  617. /// LEFT JOIN ipv6_reservations ...
  618. /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
  619. class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
  620. private:
  621. /// @brief Number of columns holding IPv6 reservation information.
  622. static const size_t RESERVATION_COLUMNS = 5;
  623. public:
  624. /// @brief Constructor.
  625. ///
  626. /// Apart from initializing the base class data structures it also
  627. /// initializes values representing IPv6 reservation information.
  628. PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
  629. : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
  630. reservation_id_index_(findAvailColumn()),
  631. address_index_(reservation_id_index_ + 1),
  632. prefix_len_index_(reservation_id_index_ + 2),
  633. type_index_(reservation_id_index_ + 3),
  634. iaid_index_(reservation_id_index_ + 4),
  635. most_recent_reservation_id_(0) {
  636. // Provide names of additional columns returned by the queries.
  637. columns_[reservation_id_index_] = "reservation_id";
  638. columns_[address_index_] = "address";
  639. columns_[prefix_len_index_] = "prefix_len";
  640. columns_[type_index_] = "type";
  641. columns_[iaid_index_] = "dhcp6_iaid";
  642. BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
  643. }
  644. /// @brief Reinitializes state information
  645. ///
  646. /// This function should be called in between statement executions.
  647. /// Deriving classes should invoke this method as well as be reset
  648. /// all of their own stateful values.
  649. void clear() {
  650. PgSqlHostWithOptionsExchange::clear();
  651. most_recent_reservation_id_ = 0;
  652. }
  653. /// @brief Returns reservation id from the row.
  654. ///
  655. /// @return Reservation id or 0 if no reservation data is fetched.
  656. uint64_t getReservationId(const PgSqlResult& r, int row) const {
  657. uint64_t resv_id = 0;
  658. if (!isColumnNull(r, row, reservation_id_index_)) {
  659. getColumnValue(r, row, reservation_id_index_, resv_id);
  660. }
  661. return (resv_id);
  662. };
  663. /// @brief Creates IPv6 reservation from the data contained in the
  664. /// currently processed row.
  665. ///
  666. /// Called after the MYSQL_BIND array created by createBindForReceive().
  667. ///
  668. /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
  669. IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
  670. // type: SMALLINT NOT NULL
  671. uint16_t tmp;
  672. getColumnValue(r, row, type_index_, tmp);
  673. // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
  674. IPv6Resrv::Type resv_type;
  675. switch (tmp) {
  676. case 0:
  677. resv_type = IPv6Resrv::TYPE_NA;
  678. break;
  679. case 2:
  680. resv_type = IPv6Resrv::TYPE_PD;
  681. break;
  682. default:
  683. isc_throw(BadValue,
  684. "invalid IPv6 reservation type returned: "
  685. << tmp << ". Only 0 or 2 are allowed.");
  686. }
  687. // address VARCHAR(39) NOT NULL
  688. isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));
  689. // prefix_len: SMALLINT NOT NULL
  690. uint16_t prefix_len;
  691. getColumnValue(r, row, prefix_len_index_, prefix_len);
  692. // @todo once we support populating iaid
  693. // iaid: INT
  694. // int iaid;
  695. // getColumnValue(r, row, iaid_index_, iaid);
  696. // Create the reservation.
  697. IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
  698. return (reservation);
  699. };
  700. /// @brief Processes one row of data fetched from a database.
  701. ///
  702. /// The processed data must contain host id, which uniquely identifies a
  703. /// host. This method creates a host and inserts it to the hosts collection
  704. /// only if the last inserted host has a different host id. This prevents
  705. /// adding duplicated hosts to the collection, assuming that processed
  706. /// rows are primarily ordered by host id column.
  707. ///
  708. /// Depending on the value of the @c fetched_options specified in the
  709. /// constructor, this method also parses options returned as a result
  710. /// of SELECT queries.
  711. ///
  712. /// For any returned row which contains IPv6 reservation information it
  713. /// checks if the reservation is not a duplicate of previously parsed
  714. /// reservation and appends the IPv6Resrv object into the host object
  715. /// if the parsed row contains new reservation information.
  716. ///
  717. /// @param [out] hosts Collection of hosts to which a new host created
  718. /// from the processed data should be inserted.
  719. /// @param r result set containing one or more rows of fetched data
  720. /// @param row index within the result set of the row to process
  721. virtual void processRowData(ConstHostCollection& hosts,
  722. const PgSqlResult& r, int row) {
  723. // Call parent class to fetch host information and options.
  724. PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
  725. // Shouldn't happen but just in case
  726. if (hosts.empty()) {
  727. isc_throw(Unexpected, "no host information while retrieving"
  728. " IPv6 reservation");
  729. }
  730. // If we have reservation id we havent' seen yet, retrive the
  731. // the reservation, adding it to the current host
  732. uint64_t reservation_id = getReservationId(r, row);
  733. if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
  734. HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
  735. host->addReservation(retrieveReservation(r, row));
  736. most_recent_reservation_id_ = reservation_id;
  737. }
  738. }
  739. private:
  740. /// @name Indexes of columns holding information about IPv6 reservations.
  741. //@{
  742. /// @brief Index of reservation_id column.
  743. size_t reservation_id_index_;
  744. /// @brief Index of address column.
  745. size_t address_index_;
  746. /// @brief Index of prefix_len column.
  747. size_t prefix_len_index_;
  748. /// @brief Index of type column.
  749. size_t type_index_;
  750. /// @brief Index of IAID column.
  751. size_t iaid_index_;
  752. //@}
  753. /// @brief Reservation id for last processed row.
  754. uint64_t most_recent_reservation_id_;
  755. };
  756. /// @brief This class is used for storing IPv6 reservations in a MySQL database.
  757. ///
  758. /// This class is only used to insert IPv6 reservations into the
  759. /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
  760. /// retrieve IPv6 reservation the @ref PgSqlIPv6HostExchange class should be
  761. /// used instead.
  762. ///
  763. /// When a new IPv6 reservation is inserted into the database, an appropriate
  764. /// host must be defined in the hosts table. An attempt to insert IPv6
  765. /// reservation for non-existing host will result in failure.
  766. class PgSqlIPv6ReservationExchange : public PgSqlExchange {
  767. private:
  768. /// @brief Set number of columns for ipv6_reservation table.
  769. static const size_t RESRV_COLUMNS = 6;
  770. public:
  771. /// @brief Constructor
  772. ///
  773. /// Initialize class members representing a single IPv6 reservation.
  774. PgSqlIPv6ReservationExchange()
  775. : PgSqlExchange(RESRV_COLUMNS),
  776. resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
  777. // Set the column names (for error messages)
  778. columns_[0] = "host_id";
  779. columns_[1] = "address";
  780. columns_[2] = "prefix_len";
  781. columns_[3] = "type";
  782. columns_[4] = "dhcp6_iaid";
  783. BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
  784. }
  785. /// @brief Populate a bind array representing an IPv6 reservation
  786. ///
  787. /// Constructs a PsqlBindArray for an IPv6 reservation to the database.
  788. ///
  789. /// @param resv The IPv6 reservation to be added to the database.
  790. /// None of the fields in the reservation are modified -
  791. /// @param host_id ID of the host to which this reservation belongs.
  792. ///
  793. /// @return pointer to newly constructed bind_array containing the
  794. /// bound values extracted the IPv6 reservation
  795. ///
  796. /// @throw DbOperationError if bind_array cannot be populated.
  797. PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
  798. const HostID& host_id) {
  799. // Store the values to ensure they remain valid.
  800. // Technically we don't need this, as currently all the values
  801. // are converted to strings and stored by the bind array.
  802. resv_ = resv;
  803. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  804. try {
  805. // address VARCHAR(39) NOT NULL
  806. bind_array->add(resv.getPrefix());
  807. // prefix_len: SMALLINT NOT NULL
  808. bind_array->add(resv.getPrefixLen());
  809. // type: SMALLINT NOT NULL
  810. // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
  811. uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
  812. bind_array->add(type);
  813. // dhcp6_iaid: INT UNSIGNED
  814. /// @todo: We don't support iaid in the IPv6Resrv yet.
  815. bind_array->addNull();
  816. // host_id: BIGINT NOT NULL
  817. bind_array->add(host_id);
  818. } catch (const std::exception& ex) {
  819. isc_throw(DbOperationError,
  820. "Could not create bind array from IPv6 Reservation: "
  821. << resv_.toText() << ", reason: " << ex.what());
  822. }
  823. return (bind_array);
  824. }
  825. private:
  826. /// @brief Object holding reservation being sent to the database.
  827. IPv6Resrv resv_;
  828. };
  829. /// @brief This class is used for inserting options into a database.
  830. ///
  831. /// This class supports inserting both DHCPv4 and DHCPv6 options.
  832. class PgSqlOptionExchange : public PgSqlExchange {
  833. private:
  834. static const int OPTION_ID_COL = 0;
  835. static const int CODE_COL = 1;
  836. static const int VALUE_COL = 2;
  837. static const int FORMATTED_VALUE_COL = 3;
  838. static const int SPACE_COL = 4;
  839. static const int PERSISTENT_COL = 5;
  840. static const int DHCP_CLIENT_CLASS_COL = 6;
  841. static const int DHCP_SUBNET_ID_COL = 7;
  842. static const int HOST_ID_COL = 8;
  843. static const int SCOPE_ID_COL = 9;
  844. /// @brief Number of columns in the tables holding options.
  845. static const size_t OPTION_COLUMNS = 10;
  846. public:
  847. /// @brief Constructor.
  848. PgSqlOptionExchange()
  849. : PgSqlExchange(OPTION_COLUMNS) {
  850. columns_[OPTION_ID_COL] = "option_id";
  851. columns_[CODE_COL] = "code";
  852. columns_[VALUE_COL] = "value";
  853. columns_[FORMATTED_VALUE_COL] = "formatted_value";
  854. columns_[SPACE_COL] = "space";
  855. columns_[PERSISTENT_COL] = "persistent";
  856. columns_[DHCP_CLIENT_CLASS_COL] = "dhcp_client_class";
  857. columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
  858. columns_[HOST_ID_COL] = "host_id";
  859. columns_[SCOPE_ID_COL] = "scope_id";
  860. BOOST_STATIC_ASSERT(9 < OPTION_COLUMNS);
  861. }
  862. /// @brief Creates binding array to insert option data into database.
  863. ///
  864. /// @param opt_desc option descriptor of the option to write
  865. /// @param opt_space name of the option space to which the option belongs
  866. /// @param host_id host id of the host to which the option belongs
  867. ///
  868. /// @return pointer to newly constructed bind_array containing the
  869. /// bound values extracted from host
  870. PsqlBindArrayPtr
  871. createBindForSend(const OptionDescriptor& opt_desc,
  872. const std::string& opt_space,
  873. const HostID& host_id) {
  874. // Hold pointer to the option to make sure it remains valid until
  875. // we complete a query.
  876. option_ = opt_desc.option_;
  877. // Create the bind-array
  878. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  879. try {
  880. // option_id: is auto_incremented so skip it
  881. // code: SMALLINT UNSIGNED NOT NULL
  882. bind_array->add(option_->getType());
  883. // value: BYTEA NULL
  884. if (opt_desc.formatted_value_.empty() &&
  885. (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
  886. // The formatted_value is empty and the option value is
  887. // non-empty so we need to prepare on-wire format for the
  888. // option and store it in the database as a BYTEA.
  889. OutputBuffer buf(opt_desc.option_->len());
  890. opt_desc.option_->pack(buf);
  891. const char* buf_ptr = static_cast<const char*>(buf.getData());
  892. value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
  893. buf_ptr + buf.getLength());
  894. value_len_ = value_.size();
  895. bind_array->add(value_);
  896. } else {
  897. // No value or formatted_value specified. In this case, the
  898. // value BYTEA should be NULL.
  899. bind_array->addNull(PsqlBindArray::BINARY_FMT);
  900. }
  901. // formatted_value: TEXT NULL,
  902. if (!opt_desc.formatted_value_.empty()) {
  903. bind_array->addTempString(opt_desc.formatted_value_);
  904. } else {
  905. bind_array->addNull();
  906. }
  907. // space: VARCHAR(128) NULL
  908. if (!opt_space.empty()) {
  909. bind_array->addTempString(opt_space);
  910. } else {
  911. bind_array->addNull();
  912. }
  913. // persistent: BOOLEAN DEFAULT false
  914. bind_array->add(opt_desc.persistent_);
  915. // host_id: INT NULL
  916. if (!host_id) {
  917. isc_throw(BadValue, "host_id cannot be null");
  918. }
  919. bind_array->add(host_id);
  920. } catch (const std::exception& ex) {
  921. isc_throw(DbOperationError,
  922. "Could not create bind array for inserting DHCP "
  923. "host option: " << option_->toText() << ", reason: "
  924. << ex.what());
  925. }
  926. return (bind_array);
  927. }
  928. private:
  929. /// @brief Option value as binary.
  930. std::vector<uint8_t> value_;
  931. /// @brief Option value length.
  932. size_t value_len_;
  933. /// @brief Pointer to currently parsed option.
  934. OptionPtr option_;
  935. };
  936. } // end of anonymous namespace
  937. namespace isc {
  938. namespace dhcp {
  939. /// @brief Implementation of the @ref PgSqlHostDataSource.
  940. class PgSqlHostDataSourceImpl {
  941. public:
  942. /// @brief Statement Tags
  943. ///
  944. /// The contents of the enum are indexes into the list of SQL statements.
  945. /// It is assumed that the order is such that the indicies of statements
  946. /// reading the database are less than those of statements modifying the
  947. /// database.
  948. enum StatementIndex {
  949. GET_HOST_DHCPID, // Gets hosts by host identifier
  950. GET_HOST_ADDR, // Gets hosts by IPv4 address
  951. GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
  952. GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
  953. GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
  954. GET_HOST_PREFIX, // Gets host by IPv6 prefix
  955. GET_VERSION, // Obtain version number
  956. INSERT_HOST, // Insert new host to collection
  957. INSERT_V6_RESRV, // Insert v6 reservation
  958. INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
  959. INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
  960. NUM_STATEMENTS // Number of statements
  961. };
  962. /// @brief Index of first statement performing write to the database.
  963. ///
  964. /// This value is used to mark border line between queries and other
  965. /// statements and statements performing write operation on the database,
  966. /// such as INSERT, DELETE, UPDATE.
  967. static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST;
  968. /// @brief Constructor.
  969. ///
  970. /// This constructor opens database connection and initializes prepared
  971. /// statements used in the queries.
  972. PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters);
  973. /// @brief Destructor.
  974. ~PgSqlHostDataSourceImpl();
  975. /// @brief Executes statements which insert a row into one of the tables.
  976. ///
  977. /// @param stindex Index of a statement being executed.
  978. /// @param bind Vector of MYSQL_BIND objects to be used when making the
  979. /// query.
  980. /// @param return_last_id flag indicating whether or not the insert
  981. /// returns the primary key of from the row inserted via " RETURNING
  982. /// <primary key> as pid" clause on the INSERT statement. The RETURNING
  983. /// clause causes the INSERT to return a result set that should consist
  984. /// of a single row with one column, the value of the primary key.
  985. /// Defaults to false.
  986. ///
  987. /// @return 0 if return_last_id is false, otherwise it returns the
  988. /// the value in the result set in the first col of the first row.
  989. ///
  990. /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
  991. uint64_t addStatement(PgSqlHostDataSourceImpl::StatementIndex stindex,
  992. PsqlBindArrayPtr& bind,
  993. const bool return_last_id = false);
  994. /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
  995. ///
  996. /// @param resv IPv6 Reservation to be added
  997. /// @param id ID of a host owning this reservation
  998. void addResv(const IPv6Resrv& resv, const HostID& id);
  999. /// @brief Inserts a single DHCP option into the database.
  1000. ///
  1001. /// @param stindex Index of a statement being executed.
  1002. /// @param opt_desc Option descriptor holding information about an option
  1003. /// to be inserted into the database.
  1004. /// @param opt_space Option space name.
  1005. /// @param subnet_id Subnet identifier.
  1006. /// @param host_id Host identifier.
  1007. void addOption(const PgSqlHostDataSourceImpl::StatementIndex& stindex,
  1008. const OptionDescriptor& opt_desc,
  1009. const std::string& opt_space,
  1010. const OptionalValue<SubnetID>& subnet_id,
  1011. const HostID& host_id);
  1012. /// @brief Inserts multiple options into the database.
  1013. ///
  1014. /// @param stindex Index of a statement being executed.
  1015. /// @param options_cfg An object holding a collection of options to be
  1016. /// inserted into the database.
  1017. /// @param host_id Host identifier retrieved using @c mysql_insert_id.
  1018. void addOptions(const StatementIndex& stindex,
  1019. const ConstCfgOptionPtr& options_cfg,
  1020. const uint64_t host_id);
  1021. /// @brief Creates collection of @ref Host objects with associated
  1022. /// information such as IPv6 reservations and/or DHCP options.
  1023. ///
  1024. /// This method performs a query which returns host information from
  1025. /// the 'hosts' table. The query may also use LEFT JOIN clause to
  1026. /// retrieve information from other tables, e.g. ipv6_reservations,
  1027. /// dhcp4_options and dhcp6_options.
  1028. /// Whether IPv6 reservations and/or options are assigned to the
  1029. /// @ref Host objects depends on the type of the exchange object.
  1030. ///
  1031. /// @param stindex Statement index.
  1032. /// @param bind Pointer to an array of MySQL bindings.
  1033. /// @param exchange Pointer to the exchange object used for the
  1034. /// particular query.
  1035. /// @param [out] result Reference to the collection of hosts returned.
  1036. /// @param single A boolean value indicating if a single host is
  1037. /// expected to be returned, or multiple hosts.
  1038. void getHostCollection(StatementIndex stindex, PsqlBindArrayPtr bind,
  1039. boost::shared_ptr<PgSqlHostExchange> exchange,
  1040. ConstHostCollection& result, bool single) const;
  1041. /// @brief Retrieves a host by subnet and client's unique identifier.
  1042. ///
  1043. /// This method is used by both PgSqlHostDataSource::get4 and
  1044. /// PgSqlHostDataSource::get6 methods.
  1045. ///
  1046. /// @param subnet_id Subnet identifier.
  1047. /// @param identifier_type Identifier type.
  1048. /// @param identifier_begin Pointer to a begining of a buffer containing
  1049. /// an identifier.
  1050. /// @param identifier_len Identifier length.
  1051. /// @param stindex Statement index.
  1052. /// @param exchange Pointer to the exchange object used for the
  1053. /// particular query.
  1054. ///
  1055. /// @return Pointer to const instance of Host or null pointer if
  1056. /// no host found.
  1057. ConstHostPtr getHost(const SubnetID& subnet_id,
  1058. const Host::IdentifierType& identifier_type,
  1059. const uint8_t* identifier_begin,
  1060. const size_t identifier_len,
  1061. StatementIndex stindex,
  1062. boost::shared_ptr<PgSqlHostExchange> exchange) const;
  1063. /// @brief Throws exception if database is read only.
  1064. ///
  1065. /// This method should be called by the methods which write to the
  1066. /// database. If the backend is operating in read-only mode this
  1067. /// method will throw exception.
  1068. ///
  1069. /// @throw DbReadOnly if backend is operating in read only mode.
  1070. void checkReadOnly() const;
  1071. /// @brief Returns PostgreSQL schema version of the open database
  1072. ///
  1073. /// @return Version number stored in the database, as a pair of unsigned
  1074. /// integers. "first" is the major version number, "second" the
  1075. /// minor number.
  1076. ///
  1077. /// @throw isc::dhcp::DbOperationError An operation on the open database
  1078. /// has failed.
  1079. std::pair<uint32_t, uint32_t> getVersion() const;
  1080. /// @brief Pointer to the object representing an exchange which
  1081. /// can be used to retrieve hosts and DHCPv4 options.
  1082. boost::shared_ptr<PgSqlHostWithOptionsExchange> host_exchange_;
  1083. /// @brief Pointer to an object representing an exchange which can
  1084. /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
  1085. boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
  1086. /// @brief Pointer to an object representing an exchange which can
  1087. /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
  1088. /// IPv6 reservations using a single query.
  1089. boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
  1090. /// @brief Pointer to an object representing an exchange which can
  1091. /// be used to insert new IPv6 reservation.
  1092. boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
  1093. /// @brief Pointer to an object representing an exchange which can
  1094. /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
  1095. /// or dhcp6_options table.
  1096. boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
  1097. /// @brief MySQL connection
  1098. PgSqlConnection conn_;
  1099. /// @brief Indicates if the database is opened in read only mode.
  1100. bool is_readonly_;
  1101. };
  1102. namespace {
  1103. /// @brief Array of tagged statements.
  1104. typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS>
  1105. TaggedStatementArray;
  1106. /// @brief Prepared PosgreSQL statements used by the backend to insert and
  1107. /// retrieve reservation data from the database.
  1108. TaggedStatementArray tagged_statements = { {
  1109. // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
  1110. // Retrieves host information, IPv6 reservations and both DHCPv4 and
  1111. // DHCPv6 options associated with the host. The LEFT JOIN clause is used
  1112. // to retrieve information from 4 different tables using a single query.
  1113. // Hence, this query returns multiple rows for a single host.
  1114. {2,
  1115. { OID_BYTEA, OID_INT2 },
  1116. "get_host_dhcpid",
  1117. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1118. " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
  1119. " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1120. " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
  1121. " o4.persistent, "
  1122. " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
  1123. " o6.persistent, "
  1124. " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
  1125. "FROM hosts AS h "
  1126. "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
  1127. "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
  1128. "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
  1129. "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
  1130. "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
  1131. },
  1132. // PgSqlHostDataSourceImpl::GET_HOST_ADDR
  1133. // Retrieves host information along with the DHCPv4 options associated with
  1134. // it. Left joining the dhcp4_options table results in multiple rows being
  1135. // returned for the same host. The host is retrieved by IPv4 address.
  1136. {1,
  1137. { OID_INT8 }, "get_host_addr",
  1138. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1139. " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1140. " h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
  1141. " o.value, o.formatted_value, o.space, o.persistent "
  1142. "FROM hosts AS h "
  1143. "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
  1144. "WHERE ipv4_address = $1 "
  1145. "ORDER BY h.host_id, o.option_id"
  1146. },
  1147. //PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
  1148. // Retrieves host information and DHCPv4 options using subnet identifier
  1149. // and client's identifier. Left joining the dhcp4_options table results in
  1150. // multiple rows being returned for the same host.
  1151. {3,
  1152. { OID_INT4, OID_INT2, OID_BYTEA },
  1153. "get_host_subid4_dhcpid",
  1154. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1155. " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1156. " h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
  1157. " o.value, o.formatted_value, o.space, o.persistent "
  1158. "FROM hosts AS h "
  1159. "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
  1160. "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
  1161. " AND h.dhcp_identifier = $3 "
  1162. "ORDER BY h.host_id, o.option_id"
  1163. },
  1164. //PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
  1165. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1166. // associated with a host. The number of rows returned is a multiplication
  1167. // of number of IPv6 reservations and DHCPv6 options.
  1168. {3,
  1169. { OID_INT4, OID_INT2, OID_BYTEA },
  1170. "get_host_subid6_dhcpid",
  1171. "SELECT h.host_id, h.dhcp_identifier, "
  1172. " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1173. " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1174. " h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1175. " o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1176. " o.persistent, "
  1177. " r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
  1178. "FROM hosts AS h "
  1179. "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
  1180. "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
  1181. "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
  1182. " AND h.dhcp_identifier = $3 "
  1183. "ORDER BY h.host_id, o.option_id, r.reservation_id"
  1184. },
  1185. //PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
  1186. // Retrieves host information and DHCPv4 options for the host using subnet
  1187. // identifier and IPv4 reservation. Left joining the dhcp4_options table
  1188. // results in multiple rows being returned for the host. The number of
  1189. // rows depends on the number of options defined for the host.
  1190. {2,
  1191. { OID_INT4, OID_INT8 },
  1192. "get_host_subid_addr",
  1193. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1194. " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1195. " h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
  1196. " o.value, o.formatted_value, o.space, o.persistent "
  1197. "FROM hosts AS h "
  1198. "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
  1199. "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
  1200. "ORDER BY h.host_id, o.option_id"
  1201. },
  1202. // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
  1203. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1204. // associated with a host using prefix and prefix length. This query
  1205. // returns host information for a single host. However, multiple rows
  1206. // are returned due to left joining IPv6 reservations and DHCPv6 options.
  1207. // The number of rows returned is multiplication of number of existing
  1208. // IPv6 reservations and DHCPv6 options.
  1209. {2,
  1210. { OID_VARCHAR, OID_INT2 },
  1211. "get_host_prefix",
  1212. "SELECT h.host_id, h.dhcp_identifier, "
  1213. " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1214. " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1215. " h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1216. " o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1217. " o.persistent, "
  1218. " r.reservation_id, r.address, r.prefix_len, r.type, "
  1219. " r.dhcp6_iaid "
  1220. "FROM hosts AS h "
  1221. "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
  1222. "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
  1223. "WHERE h.host_id = "
  1224. " (SELECT host_id FROM ipv6_reservations "
  1225. " WHERE address = $1 AND prefix_len = $2) "
  1226. "ORDER BY h.host_id, o.option_id, r.reservation_id"
  1227. },
  1228. //PgSqlHostDataSourceImpl::GET_VERSION
  1229. // Retrieves MySQL schema version.
  1230. {0,
  1231. { OID_NONE },
  1232. "get_version",
  1233. "SELECT version, minor FROM schema_version"
  1234. },
  1235. // PgSqlHostDataSourceImpl::INSERT_HOST
  1236. // Inserts a host into the 'hosts' table. Returns the inserted host id.
  1237. {8,
  1238. { OID_BYTEA, OID_INT2,
  1239. OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
  1240. OID_VARCHAR, OID_VARCHAR },
  1241. "insert_host",
  1242. "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
  1243. " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
  1244. " dhcp4_client_classes, dhcp6_client_classes) "
  1245. "VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
  1246. },
  1247. //PgSqlHostDataSourceImpl::INSERT_V6_RESRV
  1248. // Inserts a single IPv6 reservation into 'reservations' table.
  1249. {5,
  1250. { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
  1251. "insert_v6_resrv",
  1252. "INSERT INTO ipv6_reservations(address, prefix_len, type, "
  1253. " dhcp6_iaid, host_id) "
  1254. "VALUES ($1, $2, $3, $4, $5)"
  1255. },
  1256. // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
  1257. // Inserts a single DHCPv4 option into 'dhcp4_options' table.
  1258. // Using fixed scope_id = 3, which associates an option with host.
  1259. {6,
  1260. { OID_INT2, OID_BYTEA, OID_TEXT,
  1261. OID_VARCHAR, OID_BOOL, OID_INT8},
  1262. "insert_v4_host_option",
  1263. "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
  1264. " persistent, host_id, scope_id) "
  1265. "VALUES ($1, $2, $3, $4, $5, $6, 3)"
  1266. },
  1267. // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
  1268. // Inserts a single DHCPv6 option into 'dhcp6_options' table.
  1269. // Using fixed scope_id = 3, which associates an option with host.
  1270. {6,
  1271. { OID_INT2, OID_BYTEA, OID_TEXT,
  1272. OID_VARCHAR, OID_BOOL, OID_INT8},
  1273. "insert_v6_host_option",
  1274. "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
  1275. " persistent, host_id, scope_id) "
  1276. "VALUES ($1, $2, $3, $4, $5, $6, 3)"
  1277. }
  1278. }
  1279. };
  1280. }; // end anonymous namespace
  1281. PgSqlHostDataSourceImpl::
  1282. PgSqlHostDataSourceImpl(const PgSqlConnection::ParameterMap& parameters)
  1283. : host_exchange_(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY)),
  1284. host_ipv6_exchange_(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY)),
  1285. host_ipv46_exchange_(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::
  1286. DHCP4_AND_DHCP6)),
  1287. host_ipv6_reservation_exchange_(new PgSqlIPv6ReservationExchange()),
  1288. host_option_exchange_(new PgSqlOptionExchange()),
  1289. conn_(parameters),
  1290. is_readonly_(false) {
  1291. // Open the database.
  1292. conn_.openDatabase();
  1293. conn_.prepareStatements(tagged_statements.begin(),
  1294. tagged_statements.begin() + WRITE_STMTS_BEGIN);
  1295. // Check if the backend is explicitly configured to operate with
  1296. // read only access to the database.
  1297. is_readonly_ = conn_.configuredReadOnly();
  1298. // If we are using read-write mode for the database we also prepare
  1299. // statements for INSERTS etc.
  1300. if (!is_readonly_) {
  1301. conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
  1302. tagged_statements.end());
  1303. } else {
  1304. LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_READONLY);
  1305. }
  1306. }
  1307. PgSqlHostDataSourceImpl::~PgSqlHostDataSourceImpl() {
  1308. }
  1309. uint64_t
  1310. PgSqlHostDataSourceImpl::addStatement(StatementIndex stindex,
  1311. PsqlBindArrayPtr& bind_array,
  1312. const bool return_last_id) {
  1313. uint64_t last_id = 0;
  1314. PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
  1315. tagged_statements[stindex].nbparams,
  1316. &bind_array->values_[0],
  1317. &bind_array->lengths_[0],
  1318. &bind_array->formats_[0], 0));
  1319. int s = PQresultStatus(r);
  1320. if (s != PGRES_COMMAND_OK) {
  1321. // Failure: check for the special case of duplicate entry.
  1322. if (conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
  1323. isc_throw(DuplicateEntry, "Database duplicate entry error");
  1324. }
  1325. // Connection determines if the error is fatal or not, and
  1326. // throws the appropriate exception
  1327. conn_.checkStatementError(r, tagged_statements[stindex]);
  1328. }
  1329. if (return_last_id) {
  1330. PgSqlExchange::getColumnValue(r, 0, 0, last_id);
  1331. }
  1332. return (last_id);
  1333. }
  1334. void
  1335. PgSqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
  1336. const HostID& id) {
  1337. PsqlBindArrayPtr bind_array;
  1338. bind_array = host_ipv6_reservation_exchange_->createBindForSend(resv, id);
  1339. addStatement(INSERT_V6_RESRV, bind_array);
  1340. }
  1341. void
  1342. PgSqlHostDataSourceImpl::addOption(const StatementIndex& stindex,
  1343. const OptionDescriptor& opt_desc,
  1344. const std::string& opt_space,
  1345. const OptionalValue<SubnetID>&,
  1346. const HostID& id) {
  1347. PsqlBindArrayPtr bind_array;
  1348. bind_array = host_option_exchange_->createBindForSend(opt_desc, opt_space,
  1349. id);
  1350. addStatement(stindex, bind_array);
  1351. }
  1352. void
  1353. PgSqlHostDataSourceImpl::addOptions(const StatementIndex& stindex,
  1354. const ConstCfgOptionPtr& options_cfg,
  1355. const uint64_t host_id) {
  1356. // Get option space names and vendor space names and combine them within a
  1357. // single list.
  1358. std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
  1359. std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
  1360. option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
  1361. vendor_spaces.end());
  1362. // For each option space retrieve all options and insert them into the
  1363. // database.
  1364. for (std::list<std::string>::const_iterator space = option_spaces.begin();
  1365. space != option_spaces.end(); ++space) {
  1366. OptionContainerPtr options = options_cfg->getAll(*space);
  1367. if (options && !options->empty()) {
  1368. for (OptionContainer::const_iterator opt = options->begin();
  1369. opt != options->end(); ++opt) {
  1370. addOption(stindex, *opt, *space, OptionalValue<SubnetID>(),
  1371. host_id);
  1372. }
  1373. }
  1374. }
  1375. }
  1376. void
  1377. PgSqlHostDataSourceImpl::
  1378. getHostCollection(StatementIndex stindex, PsqlBindArrayPtr bind_array,
  1379. boost::shared_ptr<PgSqlHostExchange> exchange,
  1380. ConstHostCollection& result, bool single) const {
  1381. exchange->clear();
  1382. PgSqlResult r(PQexecPrepared(conn_, tagged_statements[stindex].name,
  1383. tagged_statements[stindex].nbparams,
  1384. &bind_array->values_[0],
  1385. &bind_array->lengths_[0],
  1386. &bind_array->formats_[0], 0));
  1387. conn_.checkStatementError(r, tagged_statements[stindex]);
  1388. int rows = r.getRows();
  1389. for(int row = 0; row < rows; ++row) {
  1390. exchange->processRowData(result, r, row);
  1391. if (single && result.size() > 1) {
  1392. isc_throw(MultipleRecords, "multiple records were found in the "
  1393. "database where only one was expected for query "
  1394. << tagged_statements[stindex].name);
  1395. }
  1396. }
  1397. }
  1398. ConstHostPtr
  1399. PgSqlHostDataSourceImpl::
  1400. getHost(const SubnetID& subnet_id,
  1401. const Host::IdentifierType& identifier_type,
  1402. const uint8_t* identifier_begin,
  1403. const size_t identifier_len,
  1404. StatementIndex stindex,
  1405. boost::shared_ptr<PgSqlHostExchange> exchange) const {
  1406. // Set up the WHERE clause value
  1407. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  1408. // Add the subnet id.
  1409. bind_array->add(subnet_id);
  1410. // Add the Identifier type.
  1411. bind_array->add(static_cast<uint8_t>(identifier_type));
  1412. // Add the identifier value.
  1413. bind_array->add(identifier_begin, identifier_len);
  1414. ConstHostCollection collection;
  1415. getHostCollection(stindex, bind_array, exchange, collection, true);
  1416. // Return single record if present, else clear the host.
  1417. ConstHostPtr result;
  1418. if (!collection.empty())
  1419. result = *collection.begin();
  1420. return (result);
  1421. }
  1422. std::pair<uint32_t, uint32_t> PgSqlHostDataSourceImpl::getVersion() const {
  1423. LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
  1424. DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
  1425. PgSqlResult r(PQexecPrepared(conn_, "get_version", 0, NULL, NULL, NULL, 0));
  1426. conn_.checkStatementError(r, tagged_statements[GET_VERSION]);
  1427. uint32_t version;
  1428. PgSqlExchange::getColumnValue(r, 0, 0, version);
  1429. uint32_t minor;
  1430. PgSqlExchange::getColumnValue(r, 0, 0, minor);
  1431. return (std::make_pair<uint32_t, uint32_t>(version, minor));
  1432. }
  1433. void
  1434. PgSqlHostDataSourceImpl::checkReadOnly() const {
  1435. if (is_readonly_) {
  1436. isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
  1437. " to operate in read only mode");
  1438. }
  1439. }
  1440. /*********** PgSqlHostDataSource *********************/
  1441. PgSqlHostDataSource::
  1442. PgSqlHostDataSource(const PgSqlConnection::ParameterMap& parameters)
  1443. : impl_(new PgSqlHostDataSourceImpl(parameters)) {
  1444. }
  1445. PgSqlHostDataSource::~PgSqlHostDataSource() {
  1446. delete impl_;
  1447. }
  1448. void
  1449. PgSqlHostDataSource::add(const HostPtr& host) {
  1450. // If operating in read-only mode, throw exception.
  1451. impl_->checkReadOnly();
  1452. // Initiate PostgreSQL transaction as we will have to make multiple queries
  1453. // to insert host information into multiple tables. If that fails on
  1454. // any stage, the transaction will be rolled back by the destructor of
  1455. // the PgSqlTransaction class.
  1456. PgSqlTransaction transaction(impl_->conn_);
  1457. // Create the MYSQL_BIND array for the host
  1458. PsqlBindArrayPtr bind_array = impl_->host_exchange_->createBindForSend(host);
  1459. // ... and insert the host.
  1460. uint32_t host_id = impl_->addStatement(PgSqlHostDataSourceImpl::INSERT_HOST,
  1461. bind_array, true);
  1462. // Insert DHCPv4 options.
  1463. ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
  1464. if (cfg_option4) {
  1465. impl_->addOptions(PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
  1466. cfg_option4, host_id);
  1467. }
  1468. // Insert DHCPv6 options.
  1469. ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
  1470. if (cfg_option6) {
  1471. impl_->addOptions(PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
  1472. cfg_option6, host_id);
  1473. }
  1474. // Insert IPv6 reservations.
  1475. IPv6ResrvRange v6resv = host->getIPv6Reservations();
  1476. if (std::distance(v6resv.first, v6resv.second) > 0) {
  1477. for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
  1478. ++resv) {
  1479. impl_->addResv(resv->second, host_id);
  1480. }
  1481. }
  1482. // Everything went fine, so explicitly commit the transaction.
  1483. transaction.commit();
  1484. }
  1485. ConstHostCollection
  1486. PgSqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
  1487. const DuidPtr& duid) const {
  1488. if (duid){
  1489. return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
  1490. duid->getDuid().size()));
  1491. } else if (hwaddr) {
  1492. return (getAll(Host::IDENT_HWADDR,
  1493. &hwaddr->hwaddr_[0],
  1494. hwaddr->hwaddr_.size()));
  1495. }
  1496. return (ConstHostCollection());
  1497. }
  1498. ConstHostCollection
  1499. PgSqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
  1500. const uint8_t* identifier_begin,
  1501. const size_t identifier_len) const {
  1502. // Set up the WHERE clause value
  1503. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  1504. // Identifier value.
  1505. bind_array->add(identifier_begin, identifier_len);
  1506. // Identifier type.
  1507. bind_array->add(static_cast<uint8_t>(identifier_type));
  1508. ConstHostCollection result;
  1509. impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
  1510. bind_array, impl_->host_ipv46_exchange_,
  1511. result, false);
  1512. return (result);
  1513. }
  1514. ConstHostCollection
  1515. PgSqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
  1516. // Set up the WHERE clause value
  1517. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  1518. // v4 Reservation address
  1519. bind_array->add(address);
  1520. ConstHostCollection result;
  1521. impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_ADDR, bind_array,
  1522. impl_->host_exchange_, result, false);
  1523. return (result);
  1524. }
  1525. ConstHostPtr
  1526. PgSqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
  1527. const DuidPtr& duid) const {
  1528. /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
  1529. if (hwaddr && duid) {
  1530. isc_throw(BadValue, "MySQL host data source get4() called with both"
  1531. " hwaddr and duid, only one of them is allowed");
  1532. }
  1533. if (!hwaddr && !duid) {
  1534. isc_throw(BadValue, "MySQL host data source get4() called with "
  1535. "neither hwaddr or duid specified, one of them is required");
  1536. }
  1537. // Choosing one of the identifiers
  1538. if (hwaddr) {
  1539. return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  1540. hwaddr->hwaddr_.size()));
  1541. } else if (duid) {
  1542. return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  1543. duid->getDuid().size()));
  1544. }
  1545. return (ConstHostPtr());
  1546. }
  1547. ConstHostPtr
  1548. PgSqlHostDataSource::get4(const SubnetID& subnet_id,
  1549. const Host::IdentifierType& identifier_type,
  1550. const uint8_t* identifier_begin,
  1551. const size_t identifier_len) const {
  1552. return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
  1553. identifier_len,
  1554. PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
  1555. impl_->host_exchange_));
  1556. }
  1557. ConstHostPtr
  1558. PgSqlHostDataSource::get4(const SubnetID& subnet_id,
  1559. const asiolink::IOAddress& address) const {
  1560. if (!address.isV4()) {
  1561. isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
  1562. " wrong address type, address supplied is an IPv6 address");
  1563. }
  1564. // Set up the WHERE clause value
  1565. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  1566. // Add the subnet id
  1567. bind_array->add(subnet_id);
  1568. // Add the address
  1569. bind_array->add(address);
  1570. ConstHostCollection collection;
  1571. impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
  1572. bind_array, impl_->host_exchange_, collection,
  1573. true);
  1574. // Return single record if present, else clear the host.
  1575. ConstHostPtr result;
  1576. if (!collection.empty())
  1577. result = *collection.begin();
  1578. return (result);
  1579. }
  1580. ConstHostPtr
  1581. PgSqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
  1582. const HWAddrPtr& hwaddr) const {
  1583. /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
  1584. if (hwaddr && duid) {
  1585. isc_throw(BadValue, "MySQL host data source get6() called with both"
  1586. " hwaddr and duid, only one of them is allowed");
  1587. }
  1588. if (!hwaddr && !duid) {
  1589. isc_throw(BadValue, "MySQL host data source get6() called with "
  1590. "neither hwaddr or duid specified, one of them is required");
  1591. }
  1592. // Choosing one of the identifiers
  1593. if (hwaddr) {
  1594. return (get6(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  1595. hwaddr->hwaddr_.size()));
  1596. } else if (duid) {
  1597. return (get6(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  1598. duid->getDuid().size()));
  1599. }
  1600. return (ConstHostPtr());
  1601. }
  1602. ConstHostPtr
  1603. PgSqlHostDataSource::get6(const SubnetID& subnet_id,
  1604. const Host::IdentifierType& identifier_type,
  1605. const uint8_t* identifier_begin,
  1606. const size_t identifier_len) const {
  1607. return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
  1608. identifier_len, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
  1609. impl_->host_ipv6_exchange_));
  1610. }
  1611. ConstHostPtr
  1612. PgSqlHostDataSource::get6(const asiolink::IOAddress& prefix,
  1613. const uint8_t prefix_len) const {
  1614. /// @todo: Check that prefix is v6 address, not v4.
  1615. // Set up the WHERE clause value
  1616. PsqlBindArrayPtr bind_array(new PsqlBindArray());
  1617. // Add the prefix
  1618. bind_array->add(prefix);
  1619. // Add the prefix length
  1620. bind_array->add(prefix_len);
  1621. ConstHostCollection collection;
  1622. impl_->getHostCollection(PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
  1623. bind_array, impl_->host_ipv6_exchange_,
  1624. collection, true);
  1625. // Return single record if present, else clear the host.
  1626. ConstHostPtr result;
  1627. if (!collection.empty()) {
  1628. result = *collection.begin();
  1629. }
  1630. return (result);
  1631. }
  1632. // Miscellaneous database methods.
  1633. std::string PgSqlHostDataSource::getName() const {
  1634. std::string name = "";
  1635. try {
  1636. name = impl_->conn_.getParameter("name");
  1637. } catch (...) {
  1638. // Return an empty name
  1639. }
  1640. return (name);
  1641. }
  1642. std::string PgSqlHostDataSource::getDescription() const {
  1643. return (std::string("Host data source that stores host information"
  1644. "in PostgreSQL database"));
  1645. }
  1646. std::pair<uint32_t, uint32_t> PgSqlHostDataSource::getVersion() const {
  1647. return(impl_->getVersion());
  1648. }
  1649. void
  1650. PgSqlHostDataSource::commit() {
  1651. // If operating in read-only mode, throw exception.
  1652. impl_->checkReadOnly();
  1653. impl_->conn_.commit();
  1654. }
  1655. void
  1656. PgSqlHostDataSource::rollback() {
  1657. // If operating in read-only mode, throw exception.
  1658. impl_->checkReadOnly();
  1659. impl_->conn_.rollback();
  1660. }
  1661. }; // end of isc::dhcp namespace
  1662. }; // end of isc namespace