pgsql_host_data_source.cc 80 KB

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