mysql_host_data_source.cc 99 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503
  1. // Copyright (C) 2015-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/cfg_option.h>
  12. #include <dhcpsrv/dhcpsrv_log.h>
  13. #include <dhcpsrv/mysql_host_data_source.h>
  14. #include <dhcpsrv/db_exceptions.h>
  15. #include <util/buffer.h>
  16. #include <util/optional_value.h>
  17. #include <boost/algorithm/string/split.hpp>
  18. #include <boost/algorithm/string/classification.hpp>
  19. #include <boost/pointer_cast.hpp>
  20. #include <boost/static_assert.hpp>
  21. #include <mysql.h>
  22. #include <mysqld_error.h>
  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 size of an IPv6 address represented as a text string.
  32. ///
  33. /// This is 32 hexadecimal characters written in 8 groups of four, plus seven
  34. /// colon separators.
  35. const size_t ADDRESS6_TEXT_MAX_LEN = 39;
  36. /// @brief Maximum length of classes stored in a dhcp4/6_client_classes
  37. /// columns.
  38. const size_t CLIENT_CLASSES_MAX_LEN = 255;
  39. /// @brief Maximum length of the hostname stored in DNS.
  40. ///
  41. /// This length is restricted by the length of the domain-name carried
  42. /// in the Client FQDN %Option (see RFC4702 and RFC4704).
  43. const size_t HOSTNAME_MAX_LEN = 255;
  44. /// @brief Maximum length of option value.
  45. const size_t OPTION_VALUE_MAX_LEN = 4096;
  46. /// @brief Maximum length of option value specified in textual format.
  47. const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
  48. /// @brief Maximum length of option space name.
  49. const size_t OPTION_SPACE_MAX_LEN = 128;
  50. /// @brief Numeric value representing last supported identifier.
  51. ///
  52. /// This value is used to validate whether the identifier type stored in
  53. /// a database is within bounds. of supported identifiers.
  54. const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
  55. /// @brief This class provides mechanisms for sending and retrieving
  56. /// information from the 'hosts' table.
  57. ///
  58. /// This class is used to insert and retrieve entries from the 'hosts' table.
  59. /// The queries used with this class do not retrieve IPv6 reservations or
  60. /// options associated with a host to minimize impact on performance. Other
  61. /// classes derived from @ref MySqlHostExchange should be used to retrieve
  62. /// information about IPv6 reservations and options.
  63. class MySqlHostExchange {
  64. private:
  65. /// @brief Number of columns returned for SELECT queries send by this class.
  66. static const size_t HOST_COLUMNS = 9;
  67. public:
  68. /// @brief Constructor
  69. ///
  70. /// @param additional_columns_num This value is set by the derived classes
  71. /// to indicate how many additional columns will be returned by SELECT
  72. /// queries performed by the derived class. This constructor will allocate
  73. /// resources for these columns, e.g. binding table, error indicators.
  74. MySqlHostExchange(const size_t additional_columns_num = 0)
  75. : columns_num_(HOST_COLUMNS + additional_columns_num),
  76. bind_(columns_num_), columns_(columns_num_),
  77. error_(columns_num_, MLM_FALSE), host_id_(0),
  78. dhcp_identifier_length_(0), dhcp_identifier_type_(0),
  79. dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0),
  80. hostname_length_(0), dhcp4_client_classes_length_(0),
  81. dhcp6_client_classes_length_(0),
  82. dhcp4_subnet_id_null_(MLM_FALSE),
  83. dhcp6_subnet_id_null_(MLM_FALSE),
  84. ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
  85. dhcp4_client_classes_null_(MLM_FALSE),
  86. dhcp6_client_classes_null_(MLM_FALSE) {
  87. // Fill arrays with 0 so as they don't include any garbage.
  88. memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
  89. memset(hostname_, 0, sizeof(hostname_));
  90. memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
  91. memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
  92. // Set the column names for use by this class. This only comprises
  93. // names used by the MySqlHostExchange class. Derived classes will
  94. // need to set names for the columns they use.
  95. columns_[0] = "host_id";
  96. columns_[1] = "dhcp_identifier";
  97. columns_[2] = "dhcp_identifier_type";
  98. columns_[3] = "dhcp4_subnet_id";
  99. columns_[4] = "dhcp6_subnet_id";
  100. columns_[5] = "ipv4_address";
  101. columns_[6] = "hostname";
  102. columns_[7] = "dhcp4_client_classes";
  103. columns_[8] = "dhcp6_client_classes";
  104. BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
  105. };
  106. /// @brief Virtual destructor.
  107. virtual ~MySqlHostExchange() {
  108. }
  109. /// @brief Returns index of the first uninitialized column name.
  110. ///
  111. /// This method is called by the derived classes to determine which
  112. /// column indexes are available for the derived classes within a
  113. /// binding array, error array and column names. This method
  114. /// determines the first available index by searching the first
  115. /// empty value within the columns_ vector. Previously we relied on
  116. /// the fixed values set for each class, but this was hard to maintain
  117. /// when new columns were added to the SELECT queries. It required
  118. /// modifying indexes in all derived classes.
  119. ///
  120. /// Derived classes must call this method in their constructors and
  121. /// use returned value as an index for the first column used by the
  122. /// derived class and increment this value for each subsequent column.
  123. size_t findAvailColumn() const {
  124. std::vector<std::string>::const_iterator empty_column =
  125. std::find(columns_.begin(), columns_.end(), std::string());
  126. return (std::distance(columns_.begin(), empty_column));
  127. }
  128. /// @brief Returns value of host id.
  129. ///
  130. /// This method is used by derived classes.
  131. uint64_t getHostId() const {
  132. return (host_id_);
  133. };
  134. /// @brief Set error indicators
  135. ///
  136. /// Sets the error indicator for each of the MYSQL_BIND elements. It points
  137. /// the "error" field within an element of the input array to the
  138. /// corresponding element of the passed error array.
  139. ///
  140. /// @param bind Array of BIND elements
  141. /// @param error Array of error elements. If there is an error in getting
  142. /// data associated with one of the "bind" elements, the
  143. /// corresponding element in the error array is set to MLM_TRUE.
  144. static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
  145. std::vector<my_bool>& error) {
  146. for (size_t i = 0; i < error.size(); ++i) {
  147. error[i] = MLM_FALSE;
  148. bind[i].error = reinterpret_cast<char*>(&error[i]);
  149. }
  150. };
  151. /// @brief Return columns in error
  152. ///
  153. /// If an error is returned from a fetch (in particular, a truncated
  154. /// status), this method can be called to get the names of the fields in
  155. /// error. It returns a string comprising the names of the fields
  156. /// separated by commas. In the case of there being no error indicators
  157. /// set, it returns the string "(None)".
  158. ///
  159. /// @param error Array of error elements. An element is set to MLM_TRUE
  160. /// if the corresponding column in the database is the source of
  161. /// the error.
  162. /// @param names Array of column names, the same size as the error array.
  163. /// @param count Size of each of the arrays.
  164. static std::string getColumnsInError(std::vector<my_bool>& error,
  165. const std::vector<std::string>& names) {
  166. std::string result = "";
  167. // Accumulate list of column names
  168. for (size_t i = 0; i < names.size(); ++i) {
  169. if (error[i] == MLM_TRUE) {
  170. if (!result.empty()) {
  171. result += ", ";
  172. }
  173. result += names[i];
  174. }
  175. }
  176. if (result.empty()) {
  177. result = "(None)";
  178. }
  179. return (result);
  180. };
  181. /// @brief Create MYSQL_BIND objects for Host Pointer
  182. ///
  183. /// Fills in the MYSQL_BIND array for sending data stored in the Host object
  184. /// to the database.
  185. ///
  186. /// @param host Host object to be added to the database.
  187. /// None of the fields in the host reservation are modified -
  188. /// the host data is only read.
  189. ///
  190. /// @return Vector of MySQL BIND objects representing the data to be added.
  191. std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host) {
  192. // Store host object to ensure it remains valid.
  193. host_ = host;
  194. // Initialize prior to constructing the array of MYSQL_BIND structures.
  195. // It sets all fields, including is_null, to zero, so we need to set
  196. // is_null only if it should be true. This gives up minor performance
  197. // benefit while being safe approach.
  198. memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
  199. // Set up the structures for the various components of the host structure.
  200. try {
  201. // host_id : INT UNSIGNED NOT NULL
  202. // The host_id is auto_incremented by MySQL database,
  203. // so we need to pass the NULL value
  204. host_id_ = static_cast<uint32_t>(NULL);
  205. bind_[0].buffer_type = MYSQL_TYPE_LONG;
  206. bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
  207. bind_[0].is_unsigned = MLM_TRUE;
  208. // dhcp_identifier : VARBINARY(128) NOT NULL
  209. dhcp_identifier_length_ = host->getIdentifier().size();
  210. memcpy(static_cast<void*>(dhcp_identifier_buffer_),
  211. &(host->getIdentifier())[0],
  212. host->getIdentifier().size());
  213. bind_[1].buffer_type = MYSQL_TYPE_BLOB;
  214. bind_[1].buffer = dhcp_identifier_buffer_;
  215. bind_[1].buffer_length = dhcp_identifier_length_;
  216. bind_[1].length = &dhcp_identifier_length_;
  217. // dhcp_identifier_type : TINYINT NOT NULL
  218. dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
  219. bind_[2].buffer_type = MYSQL_TYPE_TINY;
  220. bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
  221. bind_[2].is_unsigned = MLM_TRUE;
  222. // dhcp4_subnet_id : INT UNSIGNED NULL
  223. // Can't take an address of intermediate object, so let's store it
  224. // in dhcp4_subnet_id_
  225. dhcp4_subnet_id_ = host->getIPv4SubnetID();
  226. bind_[3].buffer_type = MYSQL_TYPE_LONG;
  227. bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
  228. bind_[3].is_unsigned = MLM_TRUE;
  229. // dhcp6_subnet_id : INT UNSIGNED NULL
  230. // Can't take an address of intermediate object, so let's store it
  231. // in dhcp6_subnet_id_
  232. dhcp6_subnet_id_ = host->getIPv6SubnetID();
  233. bind_[4].buffer_type = MYSQL_TYPE_LONG;
  234. bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
  235. bind_[4].is_unsigned = MLM_TRUE;
  236. // ipv4_address : INT UNSIGNED NULL
  237. // The address in the Host structure is an IOAddress object. Convert
  238. // this to an integer for storage.
  239. ipv4_address_ = static_cast<uint32_t>(host->getIPv4Reservation());
  240. bind_[5].buffer_type = MYSQL_TYPE_LONG;
  241. bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
  242. bind_[5].is_unsigned = MLM_TRUE;
  243. // bind_[5].is_null = &MLM_FALSE; // commented out for performance
  244. // reasons, see memset() above
  245. // hostname : VARCHAR(255) NULL
  246. strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
  247. hostname_length_ = host->getHostname().length();
  248. bind_[6].buffer_type = MYSQL_TYPE_STRING;
  249. bind_[6].buffer = reinterpret_cast<char*>(hostname_);
  250. bind_[6].buffer_length = hostname_length_;
  251. // dhcp4_client_classes : VARCHAR(255) NULL
  252. bind_[7].buffer_type = MYSQL_TYPE_STRING;
  253. // Override default separator to not include space after comma.
  254. string classes4_txt = host->getClientClasses4().toText(",");
  255. strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
  256. bind_[7].buffer = dhcp4_client_classes_;
  257. bind_[7].buffer_length = classes4_txt.length();
  258. // dhcp6_client_classes : VARCHAR(255) NULL
  259. bind_[8].buffer_type = MYSQL_TYPE_STRING;
  260. // Override default separator to not include space after comma.
  261. string classes6_txt = host->getClientClasses6().toText(",");
  262. strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
  263. bind_[8].buffer = dhcp6_client_classes_;
  264. bind_[8].buffer_length = classes6_txt.length();
  265. bind_[8].buffer_length = sizeof(host->getClientClasses6());
  266. } catch (const std::exception& ex) {
  267. isc_throw(DbOperationError,
  268. "Could not create bind array from Host: "
  269. << host->getHostname() << ", reason: " << ex.what());
  270. }
  271. // Add the data to the vector. Note the end element is one after the
  272. // end of the array.
  273. return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[columns_num_]));
  274. };
  275. /// @brief Create BIND array to receive Host data.
  276. ///
  277. /// Creates a MYSQL_BIND array to receive Host data from the database.
  278. /// After data is successfully received, @ref retrieveHost can be called
  279. /// to retrieve the Host object.
  280. ///
  281. /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
  282. virtual std::vector<MYSQL_BIND> createBindForReceive() {
  283. // Initialize MYSQL_BIND array.
  284. // It sets all fields, including is_null, to zero, so we need to set
  285. // is_null only if it should be true. This gives up minor performance
  286. // benefit while being safe approach. For improved readability, the
  287. // code that explicitly sets is_null is there, but is commented out.
  288. // This also takes care of seeeting bind_[X].is_null to MLM_FALSE.
  289. memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
  290. // host_id : INT UNSIGNED NOT NULL
  291. bind_[0].buffer_type = MYSQL_TYPE_LONG;
  292. bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
  293. bind_[0].is_unsigned = MLM_TRUE;
  294. // dhcp_identifier : VARBINARY(128) NOT NULL
  295. dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
  296. bind_[1].buffer_type = MYSQL_TYPE_BLOB;
  297. bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
  298. bind_[1].buffer_length = dhcp_identifier_length_;
  299. bind_[1].length = &dhcp_identifier_length_;
  300. // dhcp_identifier_type : TINYINT NOT NULL
  301. bind_[2].buffer_type = MYSQL_TYPE_TINY;
  302. bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
  303. bind_[2].is_unsigned = MLM_TRUE;
  304. // dhcp4_subnet_id : INT UNSIGNED NULL
  305. dhcp4_subnet_id_null_ = MLM_FALSE;
  306. bind_[3].buffer_type = MYSQL_TYPE_LONG;
  307. bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
  308. bind_[3].is_unsigned = MLM_TRUE;
  309. bind_[3].is_null = &dhcp4_subnet_id_null_;
  310. // dhcp6_subnet_id : INT UNSIGNED NULL
  311. dhcp6_subnet_id_null_ = MLM_FALSE;
  312. bind_[4].buffer_type = MYSQL_TYPE_LONG;
  313. bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
  314. bind_[4].is_unsigned = MLM_TRUE;
  315. bind_[4].is_null = &dhcp6_subnet_id_null_;
  316. // ipv4_address : INT UNSIGNED NULL
  317. ipv4_address_null_ = MLM_FALSE;
  318. bind_[5].buffer_type = MYSQL_TYPE_LONG;
  319. bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
  320. bind_[5].is_unsigned = MLM_TRUE;
  321. bind_[5].is_null = &ipv4_address_null_;
  322. // hostname : VARCHAR(255) NULL
  323. hostname_null_ = MLM_FALSE;
  324. hostname_length_ = sizeof(hostname_);
  325. bind_[6].buffer_type = MYSQL_TYPE_STRING;
  326. bind_[6].buffer = reinterpret_cast<char*>(hostname_);
  327. bind_[6].buffer_length = hostname_length_;
  328. bind_[6].length = &hostname_length_;
  329. bind_[6].is_null = &hostname_null_;
  330. // dhcp4_client_classes : VARCHAR(255) NULL
  331. dhcp4_client_classes_null_ = MLM_FALSE;
  332. dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
  333. bind_[7].buffer_type = MYSQL_TYPE_STRING;
  334. bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
  335. bind_[7].buffer_length = dhcp4_client_classes_length_;
  336. bind_[7].length = &dhcp4_client_classes_length_;
  337. bind_[7].is_null = &dhcp4_client_classes_null_;
  338. // dhcp6_client_classes : VARCHAR(255) NULL
  339. dhcp6_client_classes_null_ = MLM_FALSE;
  340. dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
  341. bind_[8].buffer_type = MYSQL_TYPE_STRING;
  342. bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
  343. bind_[8].buffer_length = dhcp6_client_classes_length_;
  344. bind_[8].length = &dhcp6_client_classes_length_;
  345. bind_[8].is_null = &dhcp6_client_classes_null_;
  346. // Add the error flags
  347. setErrorIndicators(bind_, error_);
  348. // Add the data to the vector. Note the end element is one after the
  349. // end of the array.
  350. return (bind_);
  351. };
  352. /// @brief Copy received data into Host object
  353. ///
  354. /// This function copies information about the host into a newly created
  355. /// @ref Host object. This method is called after @ref createBindForReceive.
  356. /// has been used.
  357. ///
  358. /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
  359. /// @ref Host object returned.
  360. HostPtr retrieveHost() {
  361. // Check if the identifier stored in the database is correct.
  362. if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
  363. isc_throw(BadValue, "invalid dhcp identifier type returned: "
  364. << static_cast<int>(dhcp_identifier_type_));
  365. }
  366. // Set the dhcp identifier type in a variable of the appropriate
  367. // data type.
  368. Host::IdentifierType type =
  369. static_cast<Host::IdentifierType>(dhcp_identifier_type_);
  370. // Set DHCPv4 subnet ID to the value returned. If NULL returned,
  371. // set to 0.
  372. SubnetID ipv4_subnet_id(0);
  373. if (dhcp4_subnet_id_null_ == MLM_FALSE) {
  374. ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
  375. }
  376. // Set DHCPv6 subnet ID to the value returned. If NULL returned,
  377. // set to 0.
  378. SubnetID ipv6_subnet_id(0);
  379. if (dhcp6_subnet_id_null_ == MLM_FALSE) {
  380. ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
  381. }
  382. // Set IPv4 address reservation if it was given, if not, set IPv4 zero
  383. // address
  384. asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
  385. if (ipv4_address_null_ == MLM_FALSE) {
  386. ipv4_reservation = asiolink::IOAddress(ipv4_address_);
  387. }
  388. // Set hostname if non NULL value returned. Otherwise, leave an
  389. // empty string.
  390. std::string hostname;
  391. if (hostname_null_ == MLM_FALSE) {
  392. hostname = std::string(hostname_, hostname_length_);
  393. }
  394. // Set DHCPv4 client classes if non NULL value returned.
  395. std::string dhcp4_client_classes;
  396. if (dhcp4_client_classes_null_ == MLM_FALSE) {
  397. dhcp4_client_classes = std::string(dhcp4_client_classes_,
  398. dhcp4_client_classes_length_);
  399. }
  400. // Set DHCPv6 client classes if non NULL value returned.
  401. std::string dhcp6_client_classes;
  402. if (dhcp6_client_classes_null_ == MLM_FALSE) {
  403. dhcp6_client_classes = std::string(dhcp6_client_classes_,
  404. dhcp6_client_classes_length_);
  405. }
  406. // Create and return Host object from the data gathered.
  407. HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
  408. type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
  409. hostname, dhcp4_client_classes, dhcp6_client_classes));
  410. h->setHostId(host_id_);
  411. return (h);
  412. };
  413. /// @brief Processes one row of data fetched from a database.
  414. ///
  415. /// The processed data must contain host id, which uniquely identifies a
  416. /// host. This method creates a host and inserts it to the hosts collection
  417. /// only if the last inserted host has a different host id. This prevents
  418. /// adding duplicated hosts to the collection, assuming that processed
  419. /// rows are primarily ordered by host id column.
  420. ///
  421. /// This method must be overriden in the derived classes to also
  422. /// retrieve IPv6 reservations and DHCP options associated with a host.
  423. ///
  424. /// @param [out] hosts Collection of hosts to which a new host created
  425. /// from the processed data should be inserted.
  426. virtual void processFetchedData(ConstHostCollection& hosts) {
  427. HostPtr host;
  428. // Add new host only if there are no hosts yet or the host id of the
  429. // most recently added host is different than the host id of the
  430. // currently processed host.
  431. if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
  432. // Create Host object from the fetched data and append it to the
  433. // collection.
  434. host = retrieveHost();
  435. hosts.push_back(host);
  436. }
  437. }
  438. /// @brief Return columns in error
  439. ///
  440. /// If an error is returned from a fetch (in particular, a truncated
  441. /// status), this method can be called to get the names of the fields in
  442. /// error. It returns a string comprising the names of the fields
  443. /// separated by commas. In the case of there being no error indicators
  444. /// set, it returns the string "(None)".
  445. ///
  446. /// @return Comma-separated list of columns in error, or the string
  447. /// "(None)".
  448. std::string getErrorColumns() {
  449. return (getColumnsInError(error_, columns_));
  450. };
  451. protected:
  452. /// Number of columns returned in queries.
  453. size_t columns_num_;
  454. /// Vector of MySQL bindings.
  455. std::vector<MYSQL_BIND> bind_;
  456. /// Column names.
  457. std::vector<std::string> columns_;
  458. /// Error array.
  459. std::vector<my_bool> error_;
  460. /// Pointer to Host object holding information to be inserted into
  461. /// Hosts table.
  462. HostPtr host_;
  463. private:
  464. /// Host identifier (primary key in Hosts table).
  465. uint64_t host_id_;
  466. /// Buffer holding client's identifier (e.g. DUID, HW address)
  467. /// in the binary format.
  468. uint8_t dhcp_identifier_buffer_[DUID::MAX_DUID_LEN];
  469. /// Length of a data in the dhcp_identifier_buffer_.
  470. size_t dhcp_identifier_length_;
  471. /// Type of the identifier in the dhcp_identifier_buffer_. This
  472. /// value corresponds to the @ref Host::IdentifierType value.
  473. uint8_t dhcp_identifier_type_;
  474. /// DHCPv4 subnet identifier.
  475. uint32_t dhcp4_subnet_id_;
  476. /// DHCPv6 subnet identifier.
  477. uint32_t dhcp6_subnet_id_;
  478. /// Reserved IPv4 address.
  479. uint32_t ipv4_address_;
  480. /// Name reserved for the host.
  481. char hostname_[HOSTNAME_MAX_LEN];
  482. /// Hostname length.
  483. unsigned long hostname_length_;
  484. /// A string holding comma separated list of DHCPv4 client classes.
  485. char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];
  486. /// A length of the string holding comma separated list of DHCPv4
  487. /// client classes.
  488. unsigned long dhcp4_client_classes_length_;
  489. /// A string holding comma separated list of DHCPv6 client classes.
  490. char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];
  491. /// A length of the string holding comma separated list of DHCPv6
  492. /// client classes.
  493. unsigned long dhcp6_client_classes_length_;
  494. /// @name Boolean values indicating if values of specific columns in
  495. /// the database are NULL.
  496. //@{
  497. /// Boolean flag indicating if the value of the DHCPv4 subnet is NULL.
  498. my_bool dhcp4_subnet_id_null_;
  499. /// Boolean flag indicating if the value of the DHCPv6 subnet is NULL.
  500. my_bool dhcp6_subnet_id_null_;
  501. /// Boolean flag indicating if the value of IPv4 reservation is NULL.
  502. my_bool ipv4_address_null_;
  503. /// Boolean flag indicating if the value if hostname is NULL.
  504. my_bool hostname_null_;
  505. /// Boolean flag indicating if the value of DHCPv4 client classes is
  506. /// NULL.
  507. my_bool dhcp4_client_classes_null_;
  508. /// Boolean flag indicating if the value of DHCPv6 client classes is
  509. /// NULL.
  510. my_bool dhcp6_client_classes_null_;
  511. //@}
  512. };
  513. /// @brief Extends base exchange class with ability to retrieve DHCP options
  514. /// from the 'dhcp4_options' and 'dhcp6_options' tables.
  515. ///
  516. /// This class provides means to retrieve both DHCPv4 and DHCPv6 options
  517. /// along with the host information. It is not used to retrieve IPv6
  518. /// reservations. The following types of queries are supported:
  519. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
  520. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
  521. /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
  522. class MySqlHostWithOptionsExchange : public MySqlHostExchange {
  523. private:
  524. /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
  525. static const size_t OPTION_COLUMNS = 6;
  526. /// @brief Receives DHCPv4 or DHCPv6 options information from the
  527. /// dhcp4_options or dhcp6_options tables respectively.
  528. ///
  529. /// The MySqlHostWithOptionsExchange class holds two respective instances
  530. /// of this class, one for receiving DHCPv4 options, one for receiving
  531. /// DHCPv6 options.
  532. ///
  533. /// The following are the basic functions of this class:
  534. /// - bind class members to specific columns in MySQL binding tables,
  535. /// - set DHCP options specific column names,
  536. /// - create instances of options retrieved from the database.
  537. ///
  538. /// The reason for isolating those functions in a separate C++ class is
  539. /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
  540. class OptionProcessor {
  541. public:
  542. /// @brief Constructor.
  543. ///
  544. /// @param universe V4 or V6. The type of the options' instances
  545. /// created by this class depends on this parameter.
  546. /// @param start_column Index of the first column to be used by this
  547. /// class.
  548. OptionProcessor(const Option::Universe& universe,
  549. const size_t start_column)
  550. : universe_(universe), start_column_(start_column), option_id_(0),
  551. code_(0), value_length_(0), formatted_value_length_(0),
  552. space_length_(0), persistent_(false), option_id_null_(MLM_FALSE),
  553. code_null_(MLM_FALSE), value_null_(MLM_FALSE),
  554. formatted_value_null_(MLM_FALSE), space_null_(MLM_FALSE),
  555. option_id_index_(start_column), code_index_(start_column_ + 1),
  556. value_index_(start_column_ + 2),
  557. formatted_value_index_(start_column_ + 3),
  558. space_index_(start_column_ + 4),
  559. persistent_index_(start_column_ + 5),
  560. most_recent_option_id_(0) {
  561. memset(value_, 0, sizeof(value_));
  562. memset(formatted_value_, 0, sizeof(formatted_value_));
  563. memset(space_, 0, sizeof(space_));
  564. }
  565. /// @brief Returns identifier of the currently processed option.
  566. uint64_t getOptionId() const {
  567. if (option_id_null_ == MLM_FALSE) {
  568. return (option_id_);
  569. }
  570. return (0);
  571. }
  572. /// @brief Creates instance of the currently processed option.
  573. ///
  574. /// This method detects if the currently processed option is a new
  575. /// instance. It makes it determination by comparing the identifier
  576. /// of the currently processed option, with the most recently processed
  577. /// option. If the current value is greater than the id of the recently
  578. /// processed option it is assumed that the processed row holds new
  579. /// option information. In such case the option instance is created and
  580. /// inserted into the configuration passed as argument.
  581. ///
  582. /// @param cfg Pointer to the configuration object into which new
  583. /// option instances should be inserted.
  584. void retrieveOption(const CfgOptionPtr& cfg) {
  585. // option_id may be NULL if dhcp4_options or dhcp6_options table
  586. // doesn't contain any options for the particular host. Also, the
  587. // current option id must be greater than id if the most recent
  588. // option because options are ordered by option id. Otherwise
  589. // we assume that this is already processed option.
  590. if ((option_id_null_ == MLM_TRUE) ||
  591. (most_recent_option_id_ >= option_id_)) {
  592. return;
  593. }
  594. // Remember current option id as the most recent processed one. We
  595. // will be comparing it with option ids in subsequent rows.
  596. most_recent_option_id_ = option_id_;
  597. // Convert it to string object for easier comparison.
  598. std::string space;
  599. if (space_null_ == MLM_FALSE) {
  600. // Typically, the string values returned by the database are not
  601. // NULL terminated.
  602. space_[space_length_] = '\0';
  603. space.assign(space_);
  604. }
  605. // Convert formatted_value to string as well.
  606. std::string formatted_value;
  607. if (formatted_value_null_ == MLM_FALSE) {
  608. formatted_value_[formatted_value_length_] = '\0';
  609. formatted_value.assign(formatted_value_);
  610. }
  611. // Options are held in a binary or textual format in the database.
  612. // This is similar to having an option specified in a server
  613. // configuration file. Such option is converted to appropriate C++
  614. // class, using option definition. Thus, we need to find the
  615. // option definition for this option code and option space.
  616. // If the option space is a standard DHCPv4 or DHCPv6 option space,
  617. // this is most likely a standard option, for which we have a
  618. // definition created within libdhcp++.
  619. OptionDefinitionPtr def;
  620. if ((space == DHCP4_OPTION_SPACE) || (space == DHCP6_OPTION_SPACE)) {
  621. def = LibDHCP::getOptionDef(universe_, code_);
  622. }
  623. // Otherwise, we may check if this an option encapsulated within the
  624. // vendor space.
  625. if (!def && (space != DHCP4_OPTION_SPACE) &&
  626. (space != DHCP6_OPTION_SPACE)) {
  627. uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
  628. if (vendor_id > 0) {
  629. def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
  630. }
  631. }
  632. // In all other cases, we use runtime option definitions, which
  633. // should be also registered within the libdhcp++.
  634. if (!def) {
  635. def = LibDHCP::getRuntimeOptionDef(space, code_);
  636. }
  637. OptionPtr option;
  638. if (!def) {
  639. // If no definition found, we use generic option type.
  640. OptionBuffer buf(value_, value_ + value_length_);
  641. option.reset(new Option(universe_, code_, buf.begin(),
  642. buf.end()));
  643. } else {
  644. // The option value may be specified in textual or binary format
  645. // in the database. If formatted_value is empty, the binary
  646. // format is used. Depending on the format we use a different
  647. // variant of the optionFactory function.
  648. if (formatted_value.empty()) {
  649. OptionBuffer buf(value_, value_ + value_length_);
  650. option = def->optionFactory(universe_, code_, buf.begin(),
  651. buf.end());
  652. } else {
  653. // Spit the value specified in comma separated values
  654. // format.
  655. std::vector<std::string> split_vec;
  656. boost::split(split_vec, formatted_value, boost::is_any_of(","));
  657. option = def->optionFactory(universe_, code_, split_vec);
  658. }
  659. }
  660. OptionDescriptor desc(option, persistent_, formatted_value);
  661. cfg->add(desc, space);
  662. }
  663. /// @brief Specify column names.
  664. ///
  665. /// @param [out] columns Reference to a vector holding names of option
  666. /// specific columns.
  667. void setColumnNames(std::vector<std::string>& columns) {
  668. columns[option_id_index_] = "option_id";
  669. columns[code_index_] = "code";
  670. columns[value_index_] = "value";
  671. columns[formatted_value_index_] = "formatted_value";
  672. columns[space_index_] = "space";
  673. columns[persistent_index_] = "persistent";
  674. }
  675. /// @brief Initialize binding table fields for options.
  676. ///
  677. /// Resets most_recent_option_id_ value to 0.
  678. ///
  679. /// @param [out] bind Binding table.
  680. void setBindFields(std::vector<MYSQL_BIND>& bind) {
  681. // This method is called just before making a new query, so we
  682. // reset the most_recent_option_id_ to start over with options
  683. // processing.
  684. most_recent_option_id_ = 0;
  685. // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
  686. bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
  687. bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
  688. bind[option_id_index_].is_unsigned = MLM_TRUE;
  689. // code : TINYINT OR SHORT UNSIGNED NOT NULL
  690. bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
  691. bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
  692. bind[code_index_].is_unsigned = MLM_TRUE;
  693. bind[code_index_].is_null = &code_null_;
  694. // value : BLOB NULL
  695. value_length_ = sizeof(value_);
  696. bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
  697. bind[value_index_].buffer = reinterpret_cast<char*>(value_);
  698. bind[value_index_].buffer_length = value_length_;
  699. bind[value_index_].length = &value_length_;
  700. bind[value_index_].is_null = &value_null_;
  701. // formatted_value : TEXT NULL
  702. formatted_value_length_ = sizeof(formatted_value_);
  703. bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
  704. bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
  705. bind[formatted_value_index_].buffer_length = formatted_value_length_;
  706. bind[formatted_value_index_].length = &formatted_value_length_;
  707. bind[formatted_value_index_].is_null = &formatted_value_null_;
  708. // space : VARCHAR(128) NULL
  709. space_length_ = sizeof(space_);
  710. bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
  711. bind[space_index_].buffer = reinterpret_cast<char*>(space_);
  712. bind[space_index_].buffer_length = space_length_;
  713. bind[space_index_].length = &space_length_;
  714. bind[space_index_].is_null = &space_null_;
  715. // persistent : TINYINT(1) NOT NULL DEFAULT 0
  716. bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
  717. bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
  718. bind[persistent_index_].is_unsigned = MLM_TRUE;
  719. }
  720. private:
  721. /// @brief Universe: V4 or V6.
  722. Option::Universe universe_;
  723. /// @brief Index of first column used by this class.
  724. size_t start_column_;
  725. /// @brief Option id.
  726. uint64_t option_id_;
  727. /// @brief Option code.
  728. uint16_t code_;
  729. /// @brief Buffer holding binary value of an option.
  730. uint8_t value_[OPTION_VALUE_MAX_LEN];
  731. /// @brief Option value length.
  732. unsigned long value_length_;
  733. /// @brief Buffer holding textual value of an option.
  734. char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
  735. /// @brief Formatted option value length.
  736. unsigned long formatted_value_length_;
  737. /// @brief Buffer holding option space name.
  738. char space_[OPTION_SPACE_MAX_LEN];
  739. /// @brief Option space length.
  740. unsigned long space_length_;
  741. /// @brief Flag indicating if option is always sent or only if
  742. /// requested.
  743. bool persistent_;
  744. /// @name Boolean values indicating if values of specific columns in
  745. /// the database are NULL.
  746. //@{
  747. /// @brief Boolean flag indicating if the DHCPv4 option id is NULL.
  748. my_bool option_id_null_;
  749. /// @brief Boolean flag indicating if the DHCPv4 option code is NULL.
  750. my_bool code_null_;
  751. /// @brief Boolean flag indicating if the DHCPv4 option value is NULL.
  752. my_bool value_null_;
  753. /// @brief Boolean flag indicating if the DHCPv4 formatted option value
  754. /// is NULL.
  755. my_bool formatted_value_null_;
  756. /// @brief Boolean flag indicating if the DHCPv4 option space is NULL.
  757. my_bool space_null_;
  758. //@}
  759. /// @name Indexes of the specific columns
  760. //@{
  761. /// @brief Option id
  762. size_t option_id_index_;
  763. /// @brief Code
  764. size_t code_index_;
  765. /// @brief Value
  766. size_t value_index_;
  767. /// @brief Formatted value
  768. size_t formatted_value_index_;
  769. /// @brief Space
  770. size_t space_index_;
  771. /// @brief Persistent
  772. size_t persistent_index_;
  773. //@}
  774. /// @brief Option id for last processed row.
  775. uint64_t most_recent_option_id_;
  776. };
  777. /// @brief Pointer to the @ref OptionProcessor class.
  778. typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
  779. public:
  780. /// @brief DHCP option types to be fetched from the database.
  781. ///
  782. /// Supported types are:
  783. /// - Only DHCPv4 options,
  784. /// - Only DHCPv6 options,
  785. /// - Both DHCPv4 and DHCPv6 options.
  786. enum FetchedOptions {
  787. DHCP4_ONLY,
  788. DHCP6_ONLY,
  789. DHCP4_AND_DHCP6
  790. };
  791. /// @brief Constructor.
  792. ///
  793. /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
  794. /// be fetched from the database for a host.
  795. /// @param additional_columns_num Number of additional columns for which
  796. /// resources should be allocated, e.g. binding table, column names etc.
  797. /// This parameter should be set to a non zero value by derived classes to
  798. /// allocate resources for the columns supported by derived classes.
  799. MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
  800. const size_t additional_columns_num = 0)
  801. : MySqlHostExchange(getRequiredColumnsNum(fetched_options)
  802. + additional_columns_num),
  803. opt_proc4_(), opt_proc6_() {
  804. // Create option processor for DHCPv4 options, if required.
  805. if ((fetched_options == DHCP4_ONLY) ||
  806. (fetched_options == DHCP4_AND_DHCP6)) {
  807. opt_proc4_.reset(new OptionProcessor(Option::V4,
  808. findAvailColumn()));
  809. opt_proc4_->setColumnNames(columns_);
  810. }
  811. // Create option processor for DHCPv6 options, if required.
  812. if ((fetched_options == DHCP6_ONLY) ||
  813. (fetched_options == DHCP4_AND_DHCP6)) {
  814. opt_proc6_.reset(new OptionProcessor(Option::V6,
  815. findAvailColumn()));
  816. opt_proc6_->setColumnNames(columns_);
  817. }
  818. }
  819. /// @brief Processes the current row.
  820. ///
  821. /// The processed row includes both host information and DHCP option
  822. /// information. Because used SELECT query use LEFT JOIN clause, the
  823. /// some rows contain duplicated host or options entries. This method
  824. /// detects duplicated information and discards such entries.
  825. ///
  826. /// @param [out] hosts Container holding parsed hosts and options.
  827. virtual void processFetchedData(ConstHostCollection& hosts) {
  828. // Holds pointer to the previously parsed host.
  829. HostPtr most_recent_host;
  830. if (!hosts.empty()) {
  831. // Const cast is not very elegant way to deal with it, but
  832. // there is a good reason to use it here. This method is called
  833. // to build a collection of const hosts to be returned to the
  834. // caller. If we wanted to use non-const collection we'd need
  835. // to copy the whole collection before returning it, which has
  836. // performance implications. Alternatively, we could store the
  837. // most recently added host in a class member but this would
  838. // make the code less readable.
  839. most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
  840. }
  841. // If no host has been parsed yet or we're at the row holding next
  842. // host, we create a new host object and put it at the end of the
  843. // list.
  844. if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
  845. HostPtr host = retrieveHost();
  846. hosts.push_back(host);
  847. most_recent_host = host;
  848. }
  849. // Parse DHCPv4 options if required to do so.
  850. if (opt_proc4_) {
  851. CfgOptionPtr cfg = most_recent_host->getCfgOption4();
  852. opt_proc4_->retrieveOption(cfg);
  853. }
  854. // Parse DHCPv6 options if required to do so.
  855. if (opt_proc6_) {
  856. CfgOptionPtr cfg = most_recent_host->getCfgOption6();
  857. opt_proc6_->retrieveOption(cfg);
  858. }
  859. }
  860. /// @brief Bind variables for receiving option data.
  861. ///
  862. /// @return Vector of MYSQL_BIND object representing data to be retrieved.
  863. virtual std::vector<MYSQL_BIND> createBindForReceive() {
  864. // The following call sets bind_ values between 0 and 8.
  865. static_cast<void>(MySqlHostExchange::createBindForReceive());
  866. // Bind variables for DHCPv4 options.
  867. if (opt_proc4_) {
  868. opt_proc4_->setBindFields(bind_);
  869. }
  870. // Bind variables for DHCPv6 options.
  871. if (opt_proc6_) {
  872. opt_proc6_->setBindFields(bind_);
  873. }
  874. // Add the error flags
  875. setErrorIndicators(bind_, error_);
  876. return (bind_);
  877. };
  878. private:
  879. /// @brief Returns a number of columns required to retrieve option data.
  880. ///
  881. /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
  882. /// DHCPv6 a different number of columns is required in the binding array.
  883. /// This method returns the number of required columns, according to the
  884. /// value of @c fetched_columns passed in the constructor.
  885. ///
  886. /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
  887. /// both types of options should be retrieved.
  888. ///
  889. /// @return Number of required columns.
  890. static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
  891. return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
  892. OPTION_COLUMNS);
  893. }
  894. /// @brief Pointer to DHCPv4 options processor.
  895. ///
  896. /// If this object is NULL, the DHCPv4 options are not fetched.
  897. OptionProcessorPtr opt_proc4_;
  898. /// @brief Pointer to DHCPv6 options processor.
  899. ///
  900. /// If this object is NULL, the DHCPv6 options are not fetched.
  901. OptionProcessorPtr opt_proc6_;
  902. };
  903. /// @brief This class provides mechanisms for sending and retrieving
  904. /// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
  905. ///
  906. /// This class extends the @ref MySqlHostWithOptionsExchange class with the
  907. /// mechanisms to retrieve IPv6 reservations. This class is used in sitations
  908. /// when it is desired to retrieve DHCPv6 specific information about the host
  909. /// (DHCPv6 options and reservations), or entire information about the host
  910. /// (DHCPv4 options, DHCPv6 options and reservations). The following are the
  911. /// queries used with this class:
  912. /// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
  913. /// LEFT JOIN ipv6_reservations ...
  914. /// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
  915. class MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
  916. private:
  917. /// @brief Number of columns holding IPv6 reservation information.
  918. static const size_t RESERVATION_COLUMNS = 5;
  919. public:
  920. /// @brief Constructor.
  921. ///
  922. /// Apart from initializing the base class data structures it also
  923. /// initializes values representing IPv6 reservation information.
  924. MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
  925. : MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
  926. reservation_id_(0),
  927. reserv_type_(0), reserv_type_null_(MLM_FALSE),
  928. ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
  929. reservation_id_index_(findAvailColumn()),
  930. address_index_(reservation_id_index_ + 1),
  931. prefix_len_index_(reservation_id_index_ + 2),
  932. type_index_(reservation_id_index_ + 3),
  933. iaid_index_(reservation_id_index_ + 4),
  934. most_recent_reservation_id_(0) {
  935. memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
  936. // Provide names of additional columns returned by the queries.
  937. columns_[reservation_id_index_] = "reservation_id";
  938. columns_[address_index_] = "address";
  939. columns_[prefix_len_index_] = "prefix_len";
  940. columns_[type_index_] = "type";
  941. columns_[iaid_index_] = "dhcp6_iaid";
  942. }
  943. /// @brief Returns last fetched reservation id.
  944. ///
  945. /// @return Reservation id or 0 if no reservation data is fetched.
  946. uint64_t getReservationId() const {
  947. if (reserv_type_null_ == MLM_FALSE) {
  948. return (reservation_id_);
  949. }
  950. return (0);
  951. };
  952. /// @brief Creates IPv6 reservation from the data contained in the
  953. /// currently processed row.
  954. ///
  955. /// Called after the MYSQL_BIND array created by createBindForReceive().
  956. ///
  957. /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
  958. IPv6Resrv retrieveReservation() {
  959. // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
  960. IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
  961. switch (reserv_type_) {
  962. case 0:
  963. type = IPv6Resrv::TYPE_NA;
  964. break;
  965. case 2:
  966. type = IPv6Resrv::TYPE_PD;
  967. break;
  968. default:
  969. isc_throw(BadValue,
  970. "invalid IPv6 reservation type returned: "
  971. << static_cast<int>(reserv_type_)
  972. << ". Only 0 or 2 are allowed.");
  973. }
  974. ipv6_address_buffer_[ipv6_address_buffer_len_] = '\0';
  975. std::string address = ipv6_address_buffer_;
  976. IPv6Resrv r(type, IOAddress(address), prefix_len_);
  977. return (r);
  978. };
  979. /// @brief Processes one row of data fetched from a database.
  980. ///
  981. /// The processed data must contain host id, which uniquely identifies a
  982. /// host. This method creates a host and inserts it to the hosts collection
  983. /// only if the last inserted host has a different host id. This prevents
  984. /// adding duplicated hosts to the collection, assuming that processed
  985. /// rows are primarily ordered by host id column.
  986. ///
  987. /// Depending on the value of the @c fetched_options specified in the
  988. /// constructor, this method also parses options returned as a result
  989. /// of SELECT queries.
  990. ///
  991. /// For any returned row which contains IPv6 reservation information it
  992. /// checks if the reservation is not a duplicate of previously parsed
  993. /// reservation and appends the IPv6Resrv object into the host object
  994. /// if the parsed row contains new reservation information.
  995. ///
  996. /// @param [out] hosts Collection of hosts to which a new host created
  997. /// from the processed data should be inserted.
  998. virtual void processFetchedData(ConstHostCollection& hosts) {
  999. // Call parent class to fetch host information and options.
  1000. MySqlHostWithOptionsExchange::processFetchedData(hosts);
  1001. if (getReservationId() == 0) {
  1002. return;
  1003. }
  1004. if (hosts.empty()) {
  1005. isc_throw(Unexpected, "no host information while retrieving"
  1006. " IPv6 reservation");
  1007. }
  1008. HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
  1009. // If we're dealing with a new reservation, let's add it to the
  1010. // host.
  1011. if (getReservationId() > most_recent_reservation_id_) {
  1012. most_recent_reservation_id_ = getReservationId();
  1013. if (most_recent_reservation_id_ > 0) {
  1014. host->addReservation(retrieveReservation());
  1015. }
  1016. }
  1017. }
  1018. /// @brief Create BIND array to receive Host data with IPv6 reservations.
  1019. ///
  1020. /// Creates a MYSQL_BIND array to receive Host data from the database.
  1021. /// After data is successfully received, @ref processedFetchedData is
  1022. /// called for each returned row to build collection of @ref Host
  1023. /// objects with associated IPv6 reservations.
  1024. ///
  1025. /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
  1026. virtual std::vector<MYSQL_BIND> createBindForReceive() {
  1027. // Reset most recent reservation id value because we're now making
  1028. // a new SELECT query.
  1029. most_recent_reservation_id_ = 0;
  1030. // Bind values supported by parent classes.
  1031. static_cast<void>(MySqlHostWithOptionsExchange::createBindForReceive());
  1032. // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
  1033. bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
  1034. bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
  1035. bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
  1036. // IPv6 address/prefix VARCHAR(39)
  1037. ipv6_address_buffer_len_ = sizeof(ipv6_address_buffer_) - 1;
  1038. bind_[address_index_].buffer_type = MYSQL_TYPE_STRING;
  1039. bind_[address_index_].buffer = ipv6_address_buffer_;
  1040. bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
  1041. bind_[address_index_].length = &ipv6_address_buffer_len_;
  1042. // prefix_len : TINYINT
  1043. bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
  1044. bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
  1045. bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
  1046. // (reservation) type : TINYINT
  1047. reserv_type_null_ = MLM_FALSE;
  1048. bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
  1049. bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
  1050. bind_[type_index_].is_unsigned = MLM_TRUE;
  1051. bind_[type_index_].is_null = &reserv_type_null_;
  1052. // dhcp6_iaid INT UNSIGNED
  1053. bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
  1054. bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
  1055. bind_[iaid_index_].is_unsigned = MLM_TRUE;
  1056. // Add the error flags
  1057. setErrorIndicators(bind_, error_);
  1058. return (bind_);
  1059. };
  1060. private:
  1061. /// @brief IPv6 reservation id.
  1062. uint64_t reservation_id_;
  1063. /// @brief IPv6 reservation type.
  1064. uint8_t reserv_type_;
  1065. /// @brief Boolean flag indicating if reservation type field is null.
  1066. ///
  1067. /// This flag is used by the class to determine if the returned row
  1068. /// contains IPv6 reservation information.
  1069. my_bool reserv_type_null_;
  1070. /// @brief Buffer holding IPv6 address/prefix in textual format.
  1071. char ipv6_address_buffer_[ADDRESS6_TEXT_MAX_LEN + 1];
  1072. /// @brief Length of the textual address representation.
  1073. size_t ipv6_address_buffer_len_;
  1074. /// @brief Length of the prefix (128 for addresses)
  1075. uint8_t prefix_len_;
  1076. /// @brief IAID.
  1077. uint8_t iaid_;
  1078. /// @name Indexes of columns holding information about IPv6 reservations.
  1079. //@{
  1080. /// @brief Index of reservation_id column.
  1081. size_t reservation_id_index_;
  1082. /// @brief Index of address column.
  1083. size_t address_index_;
  1084. /// @brief Index of prefix_len column.
  1085. size_t prefix_len_index_;
  1086. /// @brief Index of type column.
  1087. size_t type_index_;
  1088. /// @brief Index of IAID column.
  1089. size_t iaid_index_;
  1090. //@}
  1091. /// @brief Reservation id for last processed row.
  1092. uint64_t most_recent_reservation_id_;
  1093. };
  1094. /// @brief This class is used for storing IPv6 reservations in a MySQL database.
  1095. ///
  1096. /// This class is only used to insert IPv6 reservations into the
  1097. /// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
  1098. /// retrieve IPv6 reservation the @ref MySqlIPv6HostExchange class should be
  1099. /// used instead.
  1100. ///
  1101. /// When a new IPv6 reservation is inserted into the database, an appropriate
  1102. /// host must be defined in the hosts table. An attempt to insert IPv6
  1103. /// reservation for non-existing host will result in failure.
  1104. class MySqlIPv6ReservationExchange {
  1105. private:
  1106. /// @brief Set number of columns for ipv6_reservation table.
  1107. static const size_t RESRV_COLUMNS = 6;
  1108. public:
  1109. /// @brief Constructor
  1110. ///
  1111. /// Initialize class members representing a single IPv6 reservation.
  1112. MySqlIPv6ReservationExchange()
  1113. : host_id_(0), address_("::"), address_len_(0), prefix_len_(0), type_(0),
  1114. iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
  1115. // Reset error table.
  1116. std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);
  1117. // Set the column names (for error messages)
  1118. columns_[0] = "host_id";
  1119. columns_[1] = "address";
  1120. columns_[2] = "prefix_len";
  1121. columns_[3] = "type";
  1122. columns_[4] = "dhcp6_iaid";
  1123. BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
  1124. }
  1125. /// @brief Create MYSQL_BIND objects for IPv6 Reservation.
  1126. ///
  1127. /// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
  1128. /// object to the database.
  1129. ///
  1130. /// @param resv An object representing IPv6 reservation which will be
  1131. /// sent to the database.
  1132. /// None of the fields in the reservation are modified -
  1133. /// the reservation data is only read.
  1134. /// @param id ID of a host owning this reservation
  1135. ///
  1136. /// @return Vector of MySQL BIND objects representing the data to be added.
  1137. std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
  1138. const HostID& id) {
  1139. // Store the values to ensure they remain valid.
  1140. resv_ = resv;
  1141. host_id_ = id;
  1142. // Initialize prior to constructing the array of MYSQL_BIND structures.
  1143. // It sets all fields, including is_null, to zero, so we need to set
  1144. // is_null only if it should be true. This gives up minor performance
  1145. // benefit while being safe approach. For improved readability, the
  1146. // code that explicitly sets is_null is there, but is commented out.
  1147. memset(bind_, 0, sizeof(bind_));
  1148. // Set up the structures for the various components of the host structure.
  1149. try {
  1150. // address VARCHAR(39)
  1151. address_ = resv.getPrefix().toText();
  1152. address_len_ = address_.length();
  1153. bind_[0].buffer_type = MYSQL_TYPE_BLOB;
  1154. bind_[0].buffer = reinterpret_cast<char*>
  1155. (const_cast<char*>(address_.c_str()));
  1156. bind_[0].buffer_length = address_len_;
  1157. bind_[0].length = &address_len_;
  1158. // prefix_len tinyint
  1159. prefix_len_ = resv.getPrefixLen();
  1160. bind_[1].buffer_type = MYSQL_TYPE_TINY;
  1161. bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
  1162. bind_[1].is_unsigned = MLM_TRUE;
  1163. // type tinyint
  1164. // See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
  1165. type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
  1166. bind_[2].buffer_type = MYSQL_TYPE_TINY;
  1167. bind_[2].buffer = reinterpret_cast<char*>(&type_);
  1168. bind_[2].is_unsigned = MLM_TRUE;
  1169. // dhcp6_iaid INT UNSIGNED
  1170. /// @todo: We don't support iaid in the IPv6Resrv yet.
  1171. iaid_ = 0;
  1172. bind_[3].buffer_type = MYSQL_TYPE_LONG;
  1173. bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
  1174. bind_[3].is_unsigned = MLM_TRUE;
  1175. // host_id INT UNSIGNED NOT NULL
  1176. bind_[4].buffer_type = MYSQL_TYPE_LONG;
  1177. bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
  1178. bind_[4].is_unsigned = MLM_TRUE;
  1179. } catch (const std::exception& ex) {
  1180. isc_throw(DbOperationError,
  1181. "Could not create bind array from IPv6 Reservation: "
  1182. << resv_.toText() << ", reason: " << ex.what());
  1183. }
  1184. // Add the data to the vector. Note the end element is one after the
  1185. // end of the array.
  1186. // RESRV_COLUMNS -1 as we do not set reservation_id.
  1187. return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[RESRV_COLUMNS-1]));
  1188. }
  1189. private:
  1190. /// @brief Host unique identifier.
  1191. uint64_t host_id_;
  1192. /// @brief Address (or prefix).
  1193. std::string address_;
  1194. /// @brief Length of the textual address representation.
  1195. size_t address_len_;
  1196. /// @brief Length of the prefix (128 for addresses).
  1197. uint8_t prefix_len_;
  1198. /// @brief Reservation type.
  1199. uint8_t type_;
  1200. /// @brief IAID.
  1201. uint8_t iaid_;
  1202. /// @brief Object holding reservation being sent to the database.
  1203. IPv6Resrv resv_;
  1204. /// @brief Array of MySQL bindings.
  1205. MYSQL_BIND bind_[RESRV_COLUMNS];
  1206. /// @brief Array of strings holding columns names.
  1207. std::string columns_[RESRV_COLUMNS];
  1208. /// @brief Array of boolean values indicating if error occurred
  1209. /// for respective columns.
  1210. my_bool error_[RESRV_COLUMNS];
  1211. };
  1212. /// @brief This class is used for inserting options into a database.
  1213. ///
  1214. /// This class supports inserting both DHCPv4 and DHCPv6 options.
  1215. class MySqlOptionExchange {
  1216. private:
  1217. /// @brief Number of columns in the tables holding options.
  1218. static const size_t OPTION_COLUMNS = 9;
  1219. public:
  1220. /// @brief Constructor.
  1221. MySqlOptionExchange()
  1222. : type_(0), value_len_(0), formatted_value_len_(0), space_(), space_len_(0),
  1223. persistent_(false), client_class_(), client_class_len_(0),
  1224. subnet_id_(0), host_id_(0), option_() {
  1225. BOOST_STATIC_ASSERT(8 < OPTION_COLUMNS);
  1226. }
  1227. /// @brief Creates binding array to insert option data into database.
  1228. ///
  1229. /// @return Vector of MYSQL_BIND object representing an option.
  1230. std::vector<MYSQL_BIND>
  1231. createBindForSend(const OptionDescriptor& opt_desc,
  1232. const std::string& opt_space,
  1233. const OptionalValue<SubnetID>& subnet_id,
  1234. const HostID& host_id) {
  1235. // Hold pointer to the option to make sure it remains valid until
  1236. // we complete a query.
  1237. option_ = opt_desc.option_;
  1238. memset(bind_, 0, sizeof(bind_));
  1239. try {
  1240. // option_id: INT UNSIGNED NOT NULL
  1241. // The option_id is auto_incremented, so we need to pass the NULL
  1242. // value.
  1243. bind_[0].buffer_type = MYSQL_TYPE_NULL;
  1244. // code: SMALLINT UNSIGNED NOT NULL
  1245. type_ = option_->getType();
  1246. bind_[1].buffer_type = MYSQL_TYPE_SHORT;
  1247. bind_[1].buffer = reinterpret_cast<char*>(&type_);
  1248. bind_[1].is_unsigned = MLM_TRUE;
  1249. // value: BLOB NULL
  1250. if (opt_desc.formatted_value_.empty() &&
  1251. (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
  1252. // The formatted_value is empty and the option value is
  1253. // non-empty so we need to prepare on-wire format for the
  1254. // option and store it in the database as a blob.
  1255. OutputBuffer buf(opt_desc.option_->len());
  1256. opt_desc.option_->pack(buf);
  1257. const char* buf_ptr = static_cast<const char*>(buf.getData());
  1258. value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
  1259. buf_ptr + buf.getLength());
  1260. value_len_ = value_.size();
  1261. bind_[2].buffer_type = MYSQL_TYPE_BLOB;
  1262. bind_[2].buffer = &value_[0];
  1263. bind_[2].buffer_length = value_len_;
  1264. bind_[2].length = &value_len_;
  1265. } else {
  1266. // No value or formatted_value specified. In this case, the
  1267. // value blob is NULL.
  1268. value_.clear();
  1269. bind_[2].buffer_type = MYSQL_TYPE_NULL;
  1270. }
  1271. // formatted_value: TEXT NULL,
  1272. if (!opt_desc.formatted_value_.empty()) {
  1273. formatted_value_len_ = opt_desc.formatted_value_.size();
  1274. bind_[3].buffer_type = MYSQL_TYPE_STRING;
  1275. bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
  1276. bind_[3].buffer_length = formatted_value_len_;
  1277. bind_[3].length = &formatted_value_len_;
  1278. } else {
  1279. bind_[3].buffer_type = MYSQL_TYPE_NULL;
  1280. }
  1281. // space: VARCHAR(128) NULL
  1282. space_ = opt_space;
  1283. space_len_ = space_.size();
  1284. bind_[4].buffer_type = MYSQL_TYPE_STRING;
  1285. bind_[4].buffer = const_cast<char*>(space_.c_str());
  1286. bind_[4].buffer_length = space_len_;
  1287. bind_[4].length = &space_len_;
  1288. // persistent: TINYINT(1) NOT NULL DEFAULT 0
  1289. persistent_ = opt_desc.persistent_;
  1290. bind_[5].buffer_type = MYSQL_TYPE_TINY;
  1291. bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
  1292. bind_[5].is_unsigned = MLM_TRUE;
  1293. // dhcp_client_class: VARCHAR(128) NULL
  1294. /// @todo Assign actual value to client class string.
  1295. client_class_len_ = client_class_.size();
  1296. bind_[6].buffer_type = MYSQL_TYPE_STRING;
  1297. bind_[6].buffer = const_cast<char*>(client_class_.c_str());
  1298. bind_[6].buffer_length = client_class_len_;
  1299. bind_[6].length = &client_class_len_;
  1300. // dhcp4_subnet_id: INT UNSIGNED NULL
  1301. if (subnet_id.isSpecified()) {
  1302. subnet_id_ = subnet_id;
  1303. bind_[7].buffer_type = MYSQL_TYPE_LONG;
  1304. bind_[7].buffer = reinterpret_cast<char*>(subnet_id_);
  1305. bind_[7].is_unsigned = MLM_TRUE;
  1306. } else {
  1307. bind_[7].buffer_type = MYSQL_TYPE_NULL;
  1308. }
  1309. // host_id: INT UNSIGNED NOT NULL
  1310. host_id_ = host_id;
  1311. bind_[8].buffer_type = MYSQL_TYPE_LONG;
  1312. bind_[8].buffer = reinterpret_cast<char*>(&host_id_);
  1313. bind_[8].is_unsigned = MLM_TRUE;
  1314. } catch (const std::exception& ex) {
  1315. isc_throw(DbOperationError,
  1316. "Could not create bind array for inserting DHCP "
  1317. "option: " << option_->toText() << ", reason: "
  1318. << ex.what());
  1319. }
  1320. return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
  1321. }
  1322. private:
  1323. /// @brief Option type.
  1324. uint16_t type_;
  1325. /// @brief Option value as binary.
  1326. std::vector<uint8_t> value_;
  1327. /// @brief Option value length.
  1328. size_t value_len_;
  1329. /// @brief Formatted option value length.
  1330. unsigned long formatted_value_len_;
  1331. /// @brief Option space name.
  1332. std::string space_;
  1333. /// @brief Option space name length.
  1334. size_t space_len_;
  1335. /// @brief Boolean flag indicating if the option is always returned to
  1336. /// a client or only when requested.
  1337. bool persistent_;
  1338. /// @brief Client classes for the option.
  1339. std::string client_class_;
  1340. /// @brief Length of the string holding client classes for the option.
  1341. size_t client_class_len_;
  1342. /// @brief Subnet identifier.
  1343. uint32_t subnet_id_;
  1344. /// @brief Host identifier.
  1345. uint32_t host_id_;
  1346. /// @brief Pointer to currently parsed option.
  1347. OptionPtr option_;
  1348. /// @brief Array of MYSQL_BIND elements representing inserted data.
  1349. MYSQL_BIND bind_[OPTION_COLUMNS];
  1350. };
  1351. } // end of anonymous namespace
  1352. namespace isc {
  1353. namespace dhcp {
  1354. /// @brief Implementation of the @ref MySqlHostDataSource.
  1355. class MySqlHostDataSourceImpl {
  1356. public:
  1357. /// @brief Statement Tags
  1358. ///
  1359. /// The contents of the enum are indexes into the list of SQL statements
  1360. enum StatementIndex {
  1361. INSERT_HOST, // Insert new host to collection
  1362. INSERT_V6_RESRV, // Insert v6 reservation
  1363. INSERT_V4_OPTION, // Insert DHCPv4 option
  1364. INSERT_V6_OPTION, // Insert DHCPv6 option
  1365. GET_HOST_DHCPID, // Gets hosts by host identifier
  1366. GET_HOST_ADDR, // Gets hosts by IPv4 address
  1367. GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
  1368. GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
  1369. GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
  1370. GET_HOST_PREFIX, // Gets host by IPv6 prefix
  1371. GET_VERSION, // Obtain version number
  1372. NUM_STATEMENTS // Number of statements
  1373. };
  1374. /// @brief Constructor.
  1375. ///
  1376. /// This constructor opens database connection and initializes prepared
  1377. /// statements used in the queries.
  1378. MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters);
  1379. /// @brief Destructor.
  1380. ~MySqlHostDataSourceImpl();
  1381. /// @brief Executes statements which inserts a row into one of the tables.
  1382. ///
  1383. /// @param stindex Index of a statement being executed.
  1384. /// @param bind Vector of MYSQL_BIND objects to be used when making the
  1385. /// query.
  1386. ///
  1387. /// @throw isc::dhcp::DuplicateEntry Database throws duplicate entry error
  1388. void addStatement(MySqlHostDataSourceImpl::StatementIndex stindex,
  1389. std::vector<MYSQL_BIND>& bind);
  1390. /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
  1391. ///
  1392. /// @param resv IPv6 Reservation to be added
  1393. /// @param id ID of a host owning this reservation
  1394. void addResv(const IPv6Resrv& resv, const HostID& id);
  1395. /// @brief Inserts a single DHCP option into the database.
  1396. ///
  1397. /// @param stindex Index of a statement being executed.
  1398. /// @param opt_desc Option descriptor holding information about an option
  1399. /// to be inserted into the database.
  1400. /// @param opt_space Option space name.
  1401. /// @param subnet_id Subnet identifier.
  1402. /// @param host_id Host identifier.
  1403. void addOption(const MySqlHostDataSourceImpl::StatementIndex& stindex,
  1404. const OptionDescriptor& opt_desc,
  1405. const std::string& opt_space,
  1406. const OptionalValue<SubnetID>& subnet_id,
  1407. const HostID& host_id);
  1408. /// @brief Inserts multiple options into the database.
  1409. ///
  1410. /// @param stindex Index of a statement being executed.
  1411. /// @param options_cfg An object holding a collection of options to be
  1412. /// inserted into the database.
  1413. /// @param host_id Host identifier retrieved using @c mysql_insert_id.
  1414. void addOptions(const StatementIndex& stindex, const ConstCfgOptionPtr& options_cfg,
  1415. const uint64_t host_id);
  1416. /// @brief Check Error and Throw Exception
  1417. ///
  1418. /// This method invokes @ref MySqlConnection::checkError.
  1419. ///
  1420. /// @param status Status code: non-zero implies an error
  1421. /// @param index Index of statement that caused the error
  1422. /// @param what High-level description of the error
  1423. ///
  1424. /// @throw isc::dhcp::DbOperationError An operation on the open database has
  1425. /// failed.
  1426. void checkError(const int status, const StatementIndex index,
  1427. const char* what) const;
  1428. /// @brief Creates collection of @ref Host objects with associated
  1429. /// information such as IPv6 reservations and/or DHCP options.
  1430. ///
  1431. /// This method performs a query which returns host information from
  1432. /// the 'hosts' table. The query may also use LEFT JOIN clause to
  1433. /// retrieve information from other tables, e.g. ipv6_reservations,
  1434. /// dhcp4_options and dhcp6_options.
  1435. /// Whether IPv6 reservations and/or options are assigned to the
  1436. /// @ref Host objects depends on the type of the exchange object.
  1437. ///
  1438. /// @param stindex Statement index.
  1439. /// @param bind Pointer to an array of MySQL bindings.
  1440. /// @param exchange Pointer to the exchange object used for the
  1441. /// particular query.
  1442. /// @param [out] result Reference to the collection of hosts returned.
  1443. /// @param single A boolean value indicating if a single host is
  1444. /// expected to be returned, or multiple hosts.
  1445. void getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
  1446. boost::shared_ptr<MySqlHostExchange> exchange,
  1447. ConstHostCollection& result, bool single) const;
  1448. /// @brief Retrieves a host by subnet and client's unique identifier.
  1449. ///
  1450. /// This method is used by both MySqlHostDataSource::get4 and
  1451. /// MySqlHOstDataSource::get6 methods.
  1452. ///
  1453. /// @param subnet_id Subnet identifier.
  1454. /// @param identifier_type Identifier type.
  1455. /// @param identifier_begin Pointer to a begining of a buffer containing
  1456. /// an identifier.
  1457. /// @param identifier_len Identifier length.
  1458. /// @param stindex Statement index.
  1459. /// @param exchange Pointer to the exchange object used for the
  1460. /// particular query.
  1461. ///
  1462. /// @return Pointer to const instance of Host or null pointer if
  1463. /// no host found.
  1464. ConstHostPtr getHost(const SubnetID& subnet_id,
  1465. const Host::IdentifierType& identifier_type,
  1466. const uint8_t* identifier_begin,
  1467. const size_t identifier_len,
  1468. StatementIndex stindex,
  1469. boost::shared_ptr<MySqlHostExchange> exchange) const;
  1470. /// @brief Pointer to the object representing an exchange which
  1471. /// can be used to retrieve hosts and DHCPv4 options.
  1472. boost::shared_ptr<MySqlHostWithOptionsExchange> host_exchange_;
  1473. /// @brief Pointer to an object representing an exchange which can
  1474. /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
  1475. boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_exchange_;
  1476. /// @brief Pointer to an object representing an exchange which can
  1477. /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
  1478. /// IPv6 reservations using a single query.
  1479. boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv46_exchange_;
  1480. /// @brief Pointer to an object representing an exchange which can
  1481. /// be used to insert new IPv6 reservation.
  1482. boost::shared_ptr<MySqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
  1483. /// @brief Pointer to an object representing an exchange which can
  1484. /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
  1485. /// or dhcp6_options table.
  1486. boost::shared_ptr<MySqlOptionExchange> host_option_exchange_;
  1487. /// @brief MySQL connection
  1488. MySqlConnection conn_;
  1489. };
  1490. /// @brief Prepared MySQL statements used by the backend to insert and
  1491. /// retrieve hosts from the database.
  1492. TaggedStatement tagged_statements[] = {
  1493. // Inserts a host into the 'hosts' table.
  1494. {MySqlHostDataSourceImpl::INSERT_HOST,
  1495. "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
  1496. "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
  1497. "dhcp4_client_classes, dhcp6_client_classes) "
  1498. "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
  1499. // Inserts a single IPv6 reservation into 'reservations' table.
  1500. {MySqlHostDataSourceImpl::INSERT_V6_RESRV,
  1501. "INSERT INTO ipv6_reservations(address, prefix_len, type, "
  1502. "dhcp6_iaid, host_id) "
  1503. "VALUES (?,?,?,?,?)"},
  1504. // Inserts a single DHCPv4 option into 'dhcp4_options' table.
  1505. // Using fixed scope_id = 3, which associates an option with host.
  1506. {MySqlHostDataSourceImpl::INSERT_V4_OPTION,
  1507. "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
  1508. "persistent, dhcp_client_class, dhcp4_subnet_id, host_id, scope_id) "
  1509. " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
  1510. // Inserts a single DHCPv6 option into 'dhcp6_options' table.
  1511. // Using fixed scope_id = 3, which associates an option with host.
  1512. {MySqlHostDataSourceImpl::INSERT_V6_OPTION,
  1513. "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
  1514. "persistent, dhcp_client_class, dhcp6_subnet_id, host_id, scope_id) "
  1515. " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
  1516. // Retrieves host information, IPv6 reservations and both DHCPv4 and
  1517. // DHCPv6 options associated with the host. The LEFT JOIN clause is used
  1518. // to retrieve information from 4 different tables using a single query.
  1519. // Hence, this query returns multiple rows for a single host.
  1520. {MySqlHostDataSourceImpl::GET_HOST_DHCPID,
  1521. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1522. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
  1523. "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1524. "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
  1525. "o4.persistent, "
  1526. "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
  1527. "o6.persistent, "
  1528. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1529. "r.dhcp6_iaid "
  1530. "FROM hosts AS h "
  1531. "LEFT JOIN dhcp4_options AS o4 "
  1532. "ON h.host_id = o4.host_id "
  1533. "LEFT JOIN dhcp6_options AS o6 "
  1534. "ON h.host_id = o6.host_id "
  1535. "LEFT JOIN ipv6_reservations AS r "
  1536. "ON h.host_id = r.host_id "
  1537. "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
  1538. "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
  1539. // Retrieves host information along with the DHCPv4 options associated with
  1540. // it. Left joining the dhcp4_options table results in multiple rows being
  1541. // returned for the same host. The host is retrieved by IPv4 address.
  1542. {MySqlHostDataSourceImpl::GET_HOST_ADDR,
  1543. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1544. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1545. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1546. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1547. "o.persistent "
  1548. "FROM hosts AS h "
  1549. "LEFT JOIN dhcp4_options AS o "
  1550. "ON h.host_id = o.host_id "
  1551. "WHERE ipv4_address = ? "
  1552. "ORDER BY h.host_id, o.option_id"},
  1553. // Retrieves host information and DHCPv4 options using subnet identifier
  1554. // and client's identifier. Left joining the dhcp4_options table results in
  1555. // multiple rows being returned for the same host.
  1556. {MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
  1557. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1558. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1559. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1560. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1561. "o.persistent "
  1562. "FROM hosts AS h "
  1563. "LEFT JOIN dhcp4_options AS o "
  1564. "ON h.host_id = o.host_id "
  1565. "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
  1566. " AND h.dhcp_identifier = ? "
  1567. "ORDER BY h.host_id, o.option_id"},
  1568. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1569. // associated with a host. The number of rows returned is a multiplication
  1570. // of number of IPv6 reservations and DHCPv6 options.
  1571. {MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
  1572. "SELECT h.host_id, h.dhcp_identifier, "
  1573. "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1574. "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1575. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1576. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1577. "o.persistent, "
  1578. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1579. "r.dhcp6_iaid "
  1580. "FROM hosts AS h "
  1581. "LEFT JOIN dhcp6_options AS o "
  1582. "ON h.host_id = o.host_id "
  1583. "LEFT JOIN ipv6_reservations AS r "
  1584. "ON h.host_id = r.host_id "
  1585. "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
  1586. "AND h.dhcp_identifier = ? "
  1587. "ORDER BY h.host_id, o.option_id, r.reservation_id"},
  1588. // Retrieves host information and DHCPv4 options for the host using subnet
  1589. // identifier and IPv4 reservation. Left joining the dhcp4_options table
  1590. // results in multiple rows being returned for the host. The number of
  1591. // rows depends on the number of options defined for the host.
  1592. {MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
  1593. "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
  1594. "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1595. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1596. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1597. "o.persistent "
  1598. "FROM hosts AS h "
  1599. "LEFT JOIN dhcp4_options AS o "
  1600. "ON h.host_id = o.host_id "
  1601. "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
  1602. "ORDER BY h.host_id, o.option_id"},
  1603. // Retrieves host information, IPv6 reservations and DHCPv6 options
  1604. // associated with a host using prefix and prefix length. This query
  1605. // returns host information for a single host. However, multiple rows
  1606. // are returned due to left joining IPv6 reservations and DHCPv6 options.
  1607. // The number of rows returned is multiplication of number of existing
  1608. // IPv6 reservations and DHCPv6 options.
  1609. {MySqlHostDataSourceImpl::GET_HOST_PREFIX,
  1610. "SELECT h.host_id, h.dhcp_identifier, "
  1611. "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
  1612. "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
  1613. "h.dhcp4_client_classes, h.dhcp6_client_classes, "
  1614. "o.option_id, o.code, o.value, o.formatted_value, o.space, "
  1615. "o.persistent, "
  1616. "r.reservation_id, r.address, r.prefix_len, r.type, "
  1617. "r.dhcp6_iaid "
  1618. "FROM hosts AS h "
  1619. "LEFT JOIN dhcp6_options AS o "
  1620. "ON h.host_id = o.host_id "
  1621. "LEFT JOIN ipv6_reservations AS r "
  1622. "ON h.host_id = r.host_id "
  1623. "WHERE h.host_id = "
  1624. "(SELECT host_id FROM ipv6_reservations "
  1625. "WHERE address = ? AND prefix_len = ?) "
  1626. "ORDER BY h.host_id, o.option_id, r.reservation_id"},
  1627. // Retrieves MySQL schema version.
  1628. {MySqlHostDataSourceImpl::GET_VERSION,
  1629. "SELECT version, minor FROM schema_version"},
  1630. // Marks the end of the statements table.
  1631. {MySqlHostDataSourceImpl::NUM_STATEMENTS, NULL}
  1632. };
  1633. MySqlHostDataSourceImpl::
  1634. MySqlHostDataSourceImpl(const MySqlConnection::ParameterMap& parameters)
  1635. : host_exchange_(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY)),
  1636. host_ipv6_exchange_(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP6_ONLY)),
  1637. host_ipv46_exchange_(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::
  1638. DHCP4_AND_DHCP6)),
  1639. host_ipv6_reservation_exchange_(new MySqlIPv6ReservationExchange()),
  1640. host_option_exchange_(new MySqlOptionExchange()),
  1641. conn_(parameters) {
  1642. // Open the database.
  1643. conn_.openDatabase();
  1644. // Disable autocommit. To avoid a flush to disk on every commit, the global
  1645. // parameter innodb_flush_log_at_trx_commit should be set to 2. This will
  1646. // cause the changes to be written to the log, but flushed to disk in the
  1647. // background every second. Setting the parameter to that value will speed
  1648. // up the system, but at the risk of losing data if the system crashes.
  1649. my_bool result = mysql_autocommit(conn_.mysql_, 0);
  1650. if (result != 0) {
  1651. isc_throw(DbOperationError, mysql_error(conn_.mysql_));
  1652. }
  1653. // Prepare all statements likely to be used.
  1654. conn_.prepareStatements(tagged_statements, NUM_STATEMENTS);
  1655. }
  1656. MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
  1657. // Free up the prepared statements, ignoring errors. (What would we do
  1658. // about them? We're destroying this object and are not really concerned
  1659. // with errors on a database connection that is about to go away.)
  1660. for (int i = 0; i < conn_.statements_.size(); ++i) {
  1661. if (conn_.statements_[i] != NULL) {
  1662. (void) mysql_stmt_close(conn_.statements_[i]);
  1663. conn_.statements_[i] = NULL;
  1664. }
  1665. }
  1666. // There is no need to close the database in this destructor: it is
  1667. // closed in the destructor of the mysql_ member variable.
  1668. }
  1669. void
  1670. MySqlHostDataSourceImpl::addStatement(StatementIndex stindex,
  1671. std::vector<MYSQL_BIND>& bind) {
  1672. // Bind the parameters to the statement
  1673. int status = mysql_stmt_bind_param(conn_.statements_[stindex], &bind[0]);
  1674. checkError(status, stindex, "unable to bind parameters");
  1675. // Execute the statement
  1676. status = mysql_stmt_execute(conn_.statements_[stindex]);
  1677. if (status != 0) {
  1678. // Failure: check for the special case of duplicate entry.
  1679. if (mysql_errno(conn_.mysql_) == ER_DUP_ENTRY) {
  1680. isc_throw(DuplicateEntry, "Database duplicate entry error");
  1681. }
  1682. checkError(status, stindex, "unable to execute");
  1683. }
  1684. }
  1685. void
  1686. MySqlHostDataSourceImpl::addResv(const IPv6Resrv& resv,
  1687. const HostID& id) {
  1688. std::vector<MYSQL_BIND> bind =
  1689. host_ipv6_reservation_exchange_->createBindForSend(resv, id);
  1690. addStatement(INSERT_V6_RESRV, bind);
  1691. }
  1692. void
  1693. MySqlHostDataSourceImpl::addOption(const StatementIndex& stindex,
  1694. const OptionDescriptor& opt_desc,
  1695. const std::string& opt_space,
  1696. const OptionalValue<SubnetID>& subnet_id,
  1697. const HostID& id) {
  1698. std::vector<MYSQL_BIND> bind =
  1699. host_option_exchange_->createBindForSend(opt_desc, opt_space,
  1700. subnet_id, id);
  1701. addStatement(stindex, bind);
  1702. }
  1703. void
  1704. MySqlHostDataSourceImpl::addOptions(const StatementIndex& stindex,
  1705. const ConstCfgOptionPtr& options_cfg,
  1706. const uint64_t host_id) {
  1707. // Get option space names and vendor space names and combine them within a
  1708. // single list.
  1709. std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
  1710. std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
  1711. option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
  1712. vendor_spaces.end());
  1713. // For each option space retrieve all options and insert them into the
  1714. // database.
  1715. for (std::list<std::string>::const_iterator space = option_spaces.begin();
  1716. space != option_spaces.end(); ++space) {
  1717. OptionContainerPtr options = options_cfg->getAll(*space);
  1718. if (options && !options->empty()) {
  1719. for (OptionContainer::const_iterator opt = options->begin();
  1720. opt != options->end(); ++opt) {
  1721. addOption(stindex, *opt, *space, OptionalValue<SubnetID>(),
  1722. host_id);
  1723. }
  1724. }
  1725. }
  1726. }
  1727. void
  1728. MySqlHostDataSourceImpl::
  1729. checkError(const int status, const StatementIndex index,
  1730. const char* what) const {
  1731. conn_.checkError(status, index, what);
  1732. }
  1733. void
  1734. MySqlHostDataSourceImpl::
  1735. getHostCollection(StatementIndex stindex, MYSQL_BIND* bind,
  1736. boost::shared_ptr<MySqlHostExchange> exchange,
  1737. ConstHostCollection& result, bool single) const {
  1738. // Bind the selection parameters to the statement
  1739. int status = mysql_stmt_bind_param(conn_.statements_[stindex], bind);
  1740. checkError(status, stindex, "unable to bind WHERE clause parameter");
  1741. // Set up the MYSQL_BIND array for the data being returned and bind it to
  1742. // the statement.
  1743. std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
  1744. status = mysql_stmt_bind_result(conn_.statements_[stindex], &outbind[0]);
  1745. checkError(status, stindex, "unable to bind SELECT clause parameters");
  1746. // Execute the statement
  1747. status = mysql_stmt_execute(conn_.statements_[stindex]);
  1748. checkError(status, stindex, "unable to execute");
  1749. // Ensure that all the lease information is retrieved in one go to avoid
  1750. // overhead of going back and forth between client and server.
  1751. status = mysql_stmt_store_result(conn_.statements_[stindex]);
  1752. checkError(status, stindex, "unable to set up for storing all results");
  1753. // Set up the fetch "release" object to release resources associated
  1754. // with the call to mysql_stmt_fetch when this method exits, then
  1755. // retrieve the data. mysql_stmt_fetch return value equal to 0 represents
  1756. // successful data fetch.
  1757. MySqlFreeResult fetch_release(conn_.statements_[stindex]);
  1758. while ((status = mysql_stmt_fetch(conn_.statements_[stindex])) ==
  1759. MLM_MYSQL_FETCH_SUCCESS) {
  1760. try {
  1761. exchange->processFetchedData(result);
  1762. } catch (const isc::BadValue& ex) {
  1763. // Rethrow the exception with a bit more data.
  1764. isc_throw(BadValue, ex.what() << ". Statement is <" <<
  1765. conn_.text_statements_[stindex] << ">");
  1766. }
  1767. if (single && (result.size() > 1)) {
  1768. isc_throw(MultipleRecords, "multiple records were found in the "
  1769. "database where only one was expected for query "
  1770. << conn_.text_statements_[stindex]);
  1771. }
  1772. }
  1773. // How did the fetch end?
  1774. // If mysql_stmt_fetch return value is equal to 1 an error occurred.
  1775. if (status == MLM_MYSQL_FETCH_FAILURE) {
  1776. // Error - unable to fetch results
  1777. checkError(status, stindex, "unable to fetch results");
  1778. } else if (status == MYSQL_DATA_TRUNCATED) {
  1779. // Data truncated - throw an exception indicating what was at fault
  1780. isc_throw(DataTruncated, conn_.text_statements_[stindex]
  1781. << " returned truncated data: columns affected are "
  1782. << exchange->getErrorColumns());
  1783. }
  1784. }
  1785. ConstHostPtr
  1786. MySqlHostDataSourceImpl::
  1787. getHost(const SubnetID& subnet_id,
  1788. const Host::IdentifierType& identifier_type,
  1789. const uint8_t* identifier_begin,
  1790. const size_t identifier_len,
  1791. StatementIndex stindex,
  1792. boost::shared_ptr<MySqlHostExchange> exchange) const {
  1793. // Set up the WHERE clause value
  1794. MYSQL_BIND inbind[3];
  1795. memset(inbind, 0, sizeof(inbind));
  1796. uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
  1797. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  1798. inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
  1799. inbind[0].is_unsigned = MLM_TRUE;
  1800. // Identifier value.
  1801. std::vector<char> identifier_vec(identifier_begin,
  1802. identifier_begin + identifier_len);
  1803. unsigned long length = identifier_vec.size();
  1804. inbind[2].buffer_type = MYSQL_TYPE_BLOB;
  1805. inbind[2].buffer = &identifier_vec[0];
  1806. inbind[2].buffer_length = length;
  1807. inbind[2].length = &length;
  1808. // Identifier type.
  1809. char identifier_type_copy = static_cast<char>(identifier_type);
  1810. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  1811. inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
  1812. inbind[1].is_unsigned = MLM_TRUE;
  1813. ConstHostCollection collection;
  1814. getHostCollection(stindex, inbind, exchange, collection, true);
  1815. // Return single record if present, else clear the host.
  1816. ConstHostPtr result;
  1817. if (!collection.empty())
  1818. result = *collection.begin();
  1819. return (result);
  1820. }
  1821. MySqlHostDataSource::
  1822. MySqlHostDataSource(const MySqlConnection::ParameterMap& parameters)
  1823. : impl_(new MySqlHostDataSourceImpl(parameters)) {
  1824. }
  1825. MySqlHostDataSource::~MySqlHostDataSource() {
  1826. delete impl_;
  1827. }
  1828. void
  1829. MySqlHostDataSource::add(const HostPtr& host) {
  1830. // Initiate MySQL transaction as we will have to make multiple queries
  1831. // to insert host information into multiple tables. If that fails on
  1832. // any stage, the transaction will be rolled back by the destructor of
  1833. // the MySqlTransaction class.
  1834. MySqlTransaction transaction(impl_->conn_);
  1835. // Create the MYSQL_BIND array for the host
  1836. std::vector<MYSQL_BIND> bind = impl_->host_exchange_->createBindForSend(host);
  1837. // ... and insert the host.
  1838. impl_->addStatement(MySqlHostDataSourceImpl::INSERT_HOST, bind);
  1839. // Gets the last inserted hosts id
  1840. uint64_t host_id = mysql_insert_id(impl_->conn_.mysql_);
  1841. // Insert DHCPv4 options.
  1842. ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
  1843. if (cfg_option4) {
  1844. impl_->addOptions(MySqlHostDataSourceImpl::INSERT_V4_OPTION,
  1845. cfg_option4, host_id);
  1846. }
  1847. // Insert DHCPv6 options.
  1848. ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
  1849. if (cfg_option6) {
  1850. impl_->addOptions(MySqlHostDataSourceImpl::INSERT_V6_OPTION,
  1851. cfg_option6, host_id);
  1852. }
  1853. // Insert IPv6 reservations.
  1854. IPv6ResrvRange v6resv = host->getIPv6Reservations();
  1855. if (std::distance(v6resv.first, v6resv.second) > 0) {
  1856. for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
  1857. ++resv) {
  1858. impl_->addResv(resv->second, host_id);
  1859. }
  1860. }
  1861. // Everything went fine, so explicitly commit the transaction.
  1862. transaction.commit();
  1863. }
  1864. ConstHostCollection
  1865. MySqlHostDataSource::getAll(const HWAddrPtr& hwaddr,
  1866. const DuidPtr& duid) const {
  1867. if (duid){
  1868. return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
  1869. duid->getDuid().size()));
  1870. } else if (hwaddr) {
  1871. return (getAll(Host::IDENT_HWADDR,
  1872. &hwaddr->hwaddr_[0],
  1873. hwaddr->hwaddr_.size()));
  1874. }
  1875. return (ConstHostCollection());
  1876. }
  1877. ConstHostCollection
  1878. MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
  1879. const uint8_t* identifier_begin,
  1880. const size_t identifier_len) const {
  1881. // Set up the WHERE clause value
  1882. MYSQL_BIND inbind[2];
  1883. memset(inbind, 0, sizeof(inbind));
  1884. // Identifier type.
  1885. char identifier_type_copy = static_cast<char>(identifier_type);
  1886. inbind[1].buffer = &identifier_type_copy;
  1887. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  1888. inbind[1].is_unsigned = MLM_TRUE;
  1889. // Identifier value.
  1890. std::vector<char> identifier_vec(identifier_begin,
  1891. identifier_begin + identifier_len);
  1892. unsigned long int length = identifier_vec.size();
  1893. inbind[0].buffer_type = MYSQL_TYPE_BLOB;
  1894. inbind[0].buffer = &identifier_vec[0];
  1895. inbind[0].buffer_length = length;
  1896. inbind[0].length = &length;
  1897. ConstHostCollection result;
  1898. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_DHCPID, inbind,
  1899. impl_->host_ipv46_exchange_,
  1900. result, false);
  1901. return (result);
  1902. }
  1903. ConstHostCollection
  1904. MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
  1905. // Set up the WHERE clause value
  1906. MYSQL_BIND inbind[1];
  1907. memset(inbind, 0, sizeof(inbind));
  1908. uint32_t addr4 = static_cast<uint32_t>(address);
  1909. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  1910. inbind[0].buffer = reinterpret_cast<char*>(&addr4);
  1911. inbind[0].is_unsigned = MLM_TRUE;
  1912. ConstHostCollection result;
  1913. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_ADDR, inbind,
  1914. impl_->host_exchange_, result, false);
  1915. return (result);
  1916. }
  1917. ConstHostPtr
  1918. MySqlHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
  1919. const DuidPtr& duid) const {
  1920. /// @todo: Rethink the logic in BaseHostDataSource::get4(subnet, hwaddr, duid)
  1921. if (hwaddr && duid) {
  1922. isc_throw(BadValue, "MySQL host data source get4() called with both"
  1923. " hwaddr and duid, only one of them is allowed");
  1924. }
  1925. if (!hwaddr && !duid) {
  1926. isc_throw(BadValue, "MySQL host data source get4() called with "
  1927. "neither hwaddr or duid specified, one of them is required");
  1928. }
  1929. // Choosing one of the identifiers
  1930. if (hwaddr) {
  1931. return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  1932. hwaddr->hwaddr_.size()));
  1933. } else if (duid) {
  1934. return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  1935. duid->getDuid().size()));
  1936. }
  1937. return (ConstHostPtr());
  1938. }
  1939. ConstHostPtr
  1940. MySqlHostDataSource::get4(const SubnetID& subnet_id,
  1941. const Host::IdentifierType& identifier_type,
  1942. const uint8_t* identifier_begin,
  1943. const size_t identifier_len) const {
  1944. return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
  1945. identifier_len, MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
  1946. impl_->host_exchange_));
  1947. }
  1948. ConstHostPtr
  1949. MySqlHostDataSource::get4(const SubnetID& subnet_id,
  1950. const asiolink::IOAddress& address) const {
  1951. // Set up the WHERE clause value
  1952. MYSQL_BIND inbind[2];
  1953. uint32_t subnet = subnet_id;
  1954. memset(inbind, 0, sizeof(inbind));
  1955. inbind[0].buffer_type = MYSQL_TYPE_LONG;
  1956. inbind[0].buffer = reinterpret_cast<char*>(&subnet);
  1957. inbind[0].is_unsigned = MLM_TRUE;
  1958. uint32_t addr4 = static_cast<uint32_t>(address);
  1959. inbind[1].buffer_type = MYSQL_TYPE_LONG;
  1960. inbind[1].buffer = reinterpret_cast<char*>(&addr4);
  1961. inbind[1].is_unsigned = MLM_TRUE;
  1962. ConstHostCollection collection;
  1963. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
  1964. inbind, impl_->host_exchange_, collection, true);
  1965. // Return single record if present, else clear the host.
  1966. ConstHostPtr result;
  1967. if (!collection.empty())
  1968. result = *collection.begin();
  1969. return (result);
  1970. }
  1971. ConstHostPtr
  1972. MySqlHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
  1973. const HWAddrPtr& hwaddr) const {
  1974. /// @todo: Rethink the logic in BaseHostDataSource::get6(subnet, hwaddr, duid)
  1975. if (hwaddr && duid) {
  1976. isc_throw(BadValue, "MySQL host data source get6() called with both"
  1977. " hwaddr and duid, only one of them is allowed");
  1978. }
  1979. if (!hwaddr && !duid) {
  1980. isc_throw(BadValue, "MySQL host data source get6() called with "
  1981. "neither hwaddr or duid specified, one of them is required");
  1982. }
  1983. // Choosing one of the identifiers
  1984. if (hwaddr) {
  1985. return (get6(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  1986. hwaddr->hwaddr_.size()));
  1987. } else if (duid) {
  1988. return (get6(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  1989. duid->getDuid().size()));
  1990. }
  1991. return (ConstHostPtr());
  1992. }
  1993. ConstHostPtr
  1994. MySqlHostDataSource::get6(const SubnetID& subnet_id,
  1995. const Host::IdentifierType& identifier_type,
  1996. const uint8_t* identifier_begin,
  1997. const size_t identifier_len) const {
  1998. return (impl_->getHost(subnet_id, identifier_type, identifier_begin,
  1999. identifier_len, MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
  2000. impl_->host_ipv6_exchange_));
  2001. }
  2002. ConstHostPtr
  2003. MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
  2004. const uint8_t prefix_len) const {
  2005. // Set up the WHERE clause value
  2006. MYSQL_BIND inbind[2];
  2007. memset(inbind, 0, sizeof(inbind));
  2008. std::string addr6 = prefix.toText();
  2009. unsigned long addr6_length = addr6.size();
  2010. inbind[0].buffer_type = MYSQL_TYPE_BLOB;
  2011. inbind[0].buffer = reinterpret_cast<char*>
  2012. (const_cast<char*>(addr6.c_str()));
  2013. inbind[0].length = &addr6_length;
  2014. inbind[0].buffer_length = addr6_length;
  2015. uint8_t tmp = prefix_len;
  2016. inbind[1].buffer_type = MYSQL_TYPE_TINY;
  2017. inbind[1].buffer = reinterpret_cast<char*>(&tmp);
  2018. inbind[1].is_unsigned = MLM_TRUE;
  2019. ConstHostCollection collection;
  2020. impl_->getHostCollection(MySqlHostDataSourceImpl::GET_HOST_PREFIX,
  2021. inbind, impl_->host_ipv6_exchange_,
  2022. collection, true);
  2023. // Return single record if present, else clear the host.
  2024. ConstHostPtr result;
  2025. if (!collection.empty()) {
  2026. result = *collection.begin();
  2027. }
  2028. return (result);
  2029. }
  2030. // Miscellaneous database methods.
  2031. std::string MySqlHostDataSource::getName() const {
  2032. std::string name = "";
  2033. try {
  2034. name = impl_->conn_.getParameter("name");
  2035. } catch (...) {
  2036. // Return an empty name
  2037. }
  2038. return (name);
  2039. }
  2040. std::string MySqlHostDataSource::getDescription() const {
  2041. return (std::string("Host data source that stores host information"
  2042. "in MySQL database"));
  2043. }
  2044. std::pair<uint32_t, uint32_t> MySqlHostDataSource::getVersion() const {
  2045. const MySqlHostDataSourceImpl::StatementIndex stindex =
  2046. MySqlHostDataSourceImpl::GET_VERSION;
  2047. LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
  2048. DHCPSRV_MYSQL_HOST_DB_GET_VERSION);
  2049. uint32_t major; // Major version number
  2050. uint32_t minor; // Minor version number
  2051. // Execute the prepared statement
  2052. int status = mysql_stmt_execute(impl_->conn_.statements_[stindex]);
  2053. if (status != 0) {
  2054. isc_throw(DbOperationError, "unable to execute <"
  2055. << impl_->conn_.text_statements_[stindex]
  2056. << "> - reason: " << mysql_error(impl_->conn_.mysql_));
  2057. }
  2058. // Bind the output of the statement to the appropriate variables.
  2059. MYSQL_BIND bind[2];
  2060. memset(bind, 0, sizeof(bind));
  2061. bind[0].buffer_type = MYSQL_TYPE_LONG;
  2062. bind[0].is_unsigned = 1;
  2063. bind[0].buffer = &major;
  2064. bind[0].buffer_length = sizeof(major);
  2065. bind[1].buffer_type = MYSQL_TYPE_LONG;
  2066. bind[1].is_unsigned = 1;
  2067. bind[1].buffer = &minor;
  2068. bind[1].buffer_length = sizeof(minor);
  2069. status = mysql_stmt_bind_result(impl_->conn_.statements_[stindex], bind);
  2070. if (status != 0) {
  2071. isc_throw(DbOperationError, "unable to bind result set: "
  2072. << mysql_error(impl_->conn_.mysql_));
  2073. }
  2074. // Fetch the data and set up the "release" object to release associated
  2075. // resources when this method exits then retrieve the data.
  2076. // mysql_stmt_fetch return value other than 0 means error occurrence.
  2077. MySqlFreeResult fetch_release(impl_->conn_.statements_[stindex]);
  2078. status = mysql_stmt_fetch(impl_->conn_.statements_[stindex]);
  2079. if (status != 0) {
  2080. isc_throw(DbOperationError, "unable to obtain result set: "
  2081. << mysql_error(impl_->conn_.mysql_));
  2082. }
  2083. return (std::make_pair(major, minor));
  2084. }
  2085. void
  2086. MySqlHostDataSource::commit() {
  2087. impl_->conn_.commit();
  2088. }
  2089. void
  2090. MySqlHostDataSource::rollback() {
  2091. impl_->conn_.rollback();
  2092. }
  2093. }; // end of isc::dhcp namespace
  2094. }; // end of isc namespace