pgsql_host_data_source.cc 77 KB

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