pgsql_host_data_source.cc 80 KB

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