kea-custom-hooks
FeM custom hooks libraries for Kea DHCP
LocalDBClient.cpp
Go to the documentation of this file.
1#include <sstream>
2
3#include <pqxx/pqxx>
4
5#include <asiolink/io_address.h>
6#include <dhcp/hwaddr.h>
7
8#include <log/logger.h>
9#include <log/macros.h>
10
11#include "AdminDBClient.hpp"
12#include "LocalDBClient.hpp"
13#include "common_vars.hpp"
14#include "util/bytea_utils.hpp"
15
16#include "log_admindb_host_reservation_importer.h"
17#include "util/pqxx_compat.hpp"
18
19namespace
20{
21STMT_NAME_T STMT_INSERT_HOST_RESERVATION{"insert_host_reservation"};
22STMT_NAME_T STMT_DELETE_HOST_RESERVATION{"delete_host_reservation"};
23STMT_NAME_T STMT_QUERY_HOST_RESERVATION_BY_MAC{"query_host_reservation_by_mac"};
24STMT_NAME_T STMT_QUERY_HOST_RESERVATION_BY_IP{"query_host_reservation_by_ip"};
25STMT_NAME_T STMT_INSERT_TMP_HOST_RESERVATION{"insert_tmp_host_reservation"};
26STMT_NAME_T STMT_CREATE_TMP_HOST_TABLE{"create_tmp_host_table"};
27STMT_NAME_T STMT_FLUSH_TMP_HOST_TABLE{"flush_tmp_host_table"};
28} // namespace
29
30namespace ahri
31{
33{
34 LocalDBClientPrivate(const std::string& connstr) : psql(connstr)
35 {
36 std::ostringstream stmt_insert_host_reservation;
37 stmt_insert_host_reservation << "INSERT INTO hosts (dhcp_identifier, dhcp_identifier_type, "
38 "ipv4_address, dhcp4_subnet_id) "
40 << ", $2, $3);";
41 std::string stmt_insert_host_reservation_str = stmt_insert_host_reservation.str();
42 psql.prepare(STMT_INSERT_HOST_RESERVATION, stmt_insert_host_reservation_str);
43
44 std::string stmt_delete_host_reservation{
45 "DELETE FROM hosts WHERE dhcp_identifier = $1 AND ipv4_address = $2 AND dhcp4_subnet_id "
46 "= $3;"};
47 psql.prepare(STMT_DELETE_HOST_RESERVATION, stmt_delete_host_reservation);
48
49 const std::string stmt_query_host_reservation_by_ip{
50 "SELECT dhcp_identifier, dhcp_identifier_type, ipv4_address, dhcp4_subnet_id FROM hosts "
51 "WHERE ipv4_address = $1;"};
52 psql.prepare(STMT_QUERY_HOST_RESERVATION_BY_IP, stmt_query_host_reservation_by_ip);
53 const std::string stmt_query_host_reservation_by_mac{
54 "SELECT dhcp_identifier, dhcp_identifier_type, ipv4_address, dhcp4_subnet_id FROM hosts "
55 "WHERE dhcp_identifier = $1;"};
56 psql.prepare(STMT_QUERY_HOST_RESERVATION_BY_MAC, stmt_query_host_reservation_by_mac);
57
58 std::string stmt_create_tmp_host_table{
59 "CREATE TEMPORARY TABLE IF NOT EXISTS tmp_full_host_reservations("
60 "id SERIAL PRIMARY KEY NOT NULL,"
61 "mac_address BYTEA NOT NULL,"
62 "ipv4_address BIGINT NOT NULL,"
63 "subnet_id INT NOT NULL);"};
64 psql.prepare(STMT_CREATE_TMP_HOST_TABLE, stmt_create_tmp_host_table);
65
66 std::string stmt_flush_tmp_host_table{"TRUNCATE tmp_full_host_reservations;"};
67 psql.prepare(STMT_FLUSH_TMP_HOST_TABLE, stmt_flush_tmp_host_table);
68 }
71
72 void apply_host_update(pqxx::work& transaction, const AdminDBClient::HostUpdate& host_update)
73 {
74#if KCH_PQXX_MAJOR_VERSION < 7
75 const pqxx::binarystring dhcp_identifier{util::to_binstr(host_update.mac.hwaddr_)};
76#else
77 const util::byte_string dhcp_identifier{util::to_bytestr(host_update.mac.hwaddr_)};
78#endif
79 assert(host_update.ipv4.isV4());
80 const uint32_t ipv4_address{host_update.ipv4.toUint32()};
81
83 transaction.exec_prepared(STMT_INSERT_HOST_RESERVATION, dhcp_identifier, ipv4_address,
84 host_update.subnet_id);
85 else
86 transaction.exec_prepared(STMT_DELETE_HOST_RESERVATION, dhcp_identifier, ipv4_address,
87 host_update.subnet_id);
88 }
89
91 {
92 pqxx::work table_transaction{psql};
93 table_transaction.exec_prepared0(STMT_CREATE_TMP_HOST_TABLE);
94 table_transaction.exec_prepared0(STMT_FLUSH_TMP_HOST_TABLE);
95 table_transaction.commit();
96 }
97
99 {
100 // The statement must be prepared before the transaction for inserting host reservations is
101 // created
102 std::string stmt_insert_tmp_host_reservation{
103 "INSERT INTO tmp_full_host_reservations "
104 " (mac_address, ipv4_address, subnet_id) "
105 "VALUES "
106 " ($1, $2, $3);"};
107 psql.prepare(STMT_INSERT_TMP_HOST_RESERVATION, stmt_insert_tmp_host_reservation);
108 }
109
110 void insert_tmp_host_reservations(pqxx::work& transaction,
111 const std::vector<AdminDBClient::HostUpdate>& full_sync)
112 {
113 for (const auto& sync_item : full_sync) {
114#if KCH_PQXX_MAJOR_VERSION < 7
115 const bytestr_t mac_bin{util::to_binstr(sync_item.mac.hwaddr_)};
116#else
117 const bytestr_t mac_bin{util::to_bytestr(sync_item.mac.hwaddr_)};
118#endif
119 transaction.exec_prepared(STMT_INSERT_TMP_HOST_RESERVATION, mac_bin,
120 sync_item.ipv4.toUint32(), sync_item.subnet_id);
121 }
122
123 psql.unprepare(STMT_INSERT_TMP_HOST_RESERVATION);
124 }
125
126 auto compare_current_and_tmp_host_reservations(pqxx::work& transaction)
127 {
128 // ORDER BY is important to ensure that removals come first in the diff, thus avoiding
129 // UNIQUE constraint violations when applying following ADD instructions
130 std::string query_compare_host_tables{
131 "SELECT"
132 " hosts.host_id AS l_id,"
133 " hosts.dhcp_identifier AS l_mac,"
134 " hosts.ipv4_address AS l_ip,"
135 " hosts.dhcp4_subnet_id AS l_subnet,"
136 " tmp_full_host_reservations.id AS r_id,"
137 " tmp_full_host_reservations.mac_address AS r_mac,"
138 " tmp_full_host_reservations.ipv4_address AS r_ip, "
139 " tmp_full_host_reservations.subnet_id AS r_subnet "
140 "FROM hosts "
141 "FULL OUTER JOIN tmp_full_host_reservations "
142 "ON hosts.dhcp_identifier = tmp_full_host_reservations.mac_address "
143 "AND hosts.ipv4_address = tmp_full_host_reservations.ipv4_address "
144 "AND hosts.dhcp4_subnet_id = tmp_full_host_reservations.subnet_id "
145 "WHERE hosts.host_id IS NULL "
146 "OR tmp_full_host_reservations.id IS NULL "
147 "ORDER BY l_id ASC "
148 ";"};
149 return transaction.exec(query_compare_host_tables);
150 }
151
153 {
154 if (row.at("r_id").is_null()) {
155#if KCH_PQXX_MAJOR_VERSION < 7
156 const bytestr_t mac_raw{row.at("l_mac")};
157#else
158 const auto mac_raw = row.at("l_mac").as<bytestr_t>();
159#endif
161 isc::asiolink::IOAddress{row.at("l_ip").as<uint32_t>()},
162 isc::dhcp::HWAddr{util::to_bytea(mac_raw), isc::dhcp::HTYPE_ETHER},
163 row.at("l_subnet").as<uint32_t>()};
164 }
165
166 if (row.at("l_id").is_null()) {
167#if KCH_PQXX_MAJOR_VERSION < 7
168 const bytestr_t mac_raw{row.at("r_mac")};
169#else
170 const auto mac_raw = row.at("r_mac").as<bytestr_t>();
171#endif
173 isc::asiolink::IOAddress{row.at("r_ip").as<uint32_t>()},
174 isc::dhcp::HWAddr{util::to_bytea(mac_raw), isc::dhcp::HTYPE_ETHER},
175 row.at("r_subnet").as<uint32_t>()};
176 }
177
178 throw std::runtime_error(__FILE__ ": compare_full_sync(): lines in JOIN without NULL id");
179 }
180
182 {
183#if KCH_PQXX_MAJOR_VERSION < 7
184 const bytestr_t mac_raw{row.at("dhcp_identifier")};
185#else
186 const auto mac_raw = row.at("dhcp_identifier").as<bytestr_t>();
187#endif
188 const auto mac = util::to_bytea(mac_raw);
189 const auto ip_raw = row.at("ipv4_address").as<uint32_t>();
190 const auto subnet_id = row.at("dhcp4_subnet_id").as<uint32_t>();
191
192 return {AdminDBClient::HostUpdate::Type::ADD, isc::asiolink::IOAddress{ip_raw},
193 isc::dhcp::HWAddr{mac, isc::dhcp::HTYPE_ETHER}, subnet_id};
194 }
195
196 static void remove_tmp_host_table(pqxx::work& transaction)
197 {
198 transaction.exec0("DROP TABLE tmp_full_host_reservations;");
199 }
200
201 pqxx::connection psql;
202};
203
204LocalDBClient::LocalDBClient(std::string_view host, std::string_view port,
205 std::string_view username, std::string_view password,
206 std::string_view database)
207{
208 std::ostringstream connstr;
209 connstr << "postgresql://" << username << ':' << password << '@' << host << ':' << port << '/'
210 << database;
211 p_impl = std::make_unique<LocalDBClientPrivate>(connstr.str());
212}
213
215
217{
218 pqxx::work transaction{p_impl->psql};
219 p_impl->apply_host_update(transaction, host_update);
220 transaction.commit();
221}
222
223void LocalDBClient::apply_host_updates(const std::vector<AdminDBClient::HostUpdate>& host_updates)
224{
225 pqxx::work transaction{p_impl->psql};
226 for (const auto& u : host_updates) {
227 LOG_INFO(ahri::globals::logger, ahri::AHRI_HOST_UPDATE_INFO)
228 .arg(u.mac.toText(false))
229 .arg(u.ipv4.toText())
230 .arg(u.incremental_update_id)
231 .arg(u.subnet_id)
232 .arg((u.type == ahri::AdminDBClient::HostUpdate::Type::ADD) ? "ADD" : "REMOVE");
233 p_impl->apply_host_update(transaction, u);
234 }
235 transaction.commit();
236}
237
238std::vector<AdminDBClient::HostUpdate>
239LocalDBClient::compare_full_sync(const std::vector<AdminDBClient::HostUpdate>& full_sync)
240{
241 p_impl->setup_tmp_host_table();
242 p_impl->prepare_insert_tmp_host_reservations();
243 pqxx::work transaction{p_impl->psql};
244 p_impl->insert_tmp_host_reservations(transaction, full_sync);
245
246 const pqxx::result differing_rows =
247 p_impl->compare_current_and_tmp_host_reservations(transaction);
248 std::vector<AdminDBClient::HostUpdate> diff;
249 diff.reserve(differing_rows.size());
250 for (const pqxx::row& row : differing_rows) {
252 }
253
255 transaction.commit();
256 return diff;
257}
258
259std::vector<AdminDBClient::HostUpdate>
260LocalDBClient::query_host_reservation(const isc::dhcp::HWAddr& mac)
261{
262 pqxx::work txn{p_impl->psql};
263
264#if KCH_PQXX_MAJOR_VERSION < 7
265 const bytestr_t mac_bin = util::to_binstr(mac.hwaddr_);
266#else
267 const bytestr_t mac_bin = util::to_bytestr(mac.hwaddr_);
268#endif
269
270 const pqxx::result res = txn.exec_prepared(STMT_QUERY_HOST_RESERVATION_BY_MAC, mac_bin);
271 std::vector<AdminDBClient::HostUpdate> hosts;
272
273 for (const pqxx::row& row : res) {
274 hosts.push_back(p_impl->make_host_update_from_hosts_row(row));
275 }
276
277 return hosts;
278}
279
280std::vector<AdminDBClient::HostUpdate>
281LocalDBClient::query_host_reservation([[maybe_unused]] const isc::asiolink::IOAddress& ip_addr)
282{
283 pqxx::work txn{p_impl->psql};
284
285 const pqxx::result res =
286 txn.exec_prepared(STMT_QUERY_HOST_RESERVATION_BY_IP, ip_addr.toUint32());
287 std::vector<AdminDBClient::HostUpdate> hosts;
288
289 for (const pqxx::row& row : res) {
291 }
292
293 return hosts;
294}
295} // namespace ahri
Byte array conversion utilities.
LocalDBClient(std::string_view host, std::string_view port, std::string_view username, std::string_view password, std::string_view database)
void apply_host_updates(const std::vector< AdminDBClient::HostUpdate > &host_updates)
Apply a bunch of host updates to the local Kea database.
std::vector< AdminDBClient::HostUpdate > compare_full_sync(const std::vector< AdminDBClient::HostUpdate > &full_sync)
Compare the current database state to the result of a full sync from AdminDB.
static constexpr int DHCP_IDENTIFIER_TYPE_MAC
Kea DHCP identifier type MAC-adresss.
void apply_host_update(const AdminDBClient::HostUpdate &host_update)
Apply a host update to the local Kea database.
std::vector< AdminDBClient::HostUpdate > query_host_reservation(const isc::dhcp::HWAddr &mac)
Query the database for host reservations with the given MAC.
Collection of common variables used throughout the library.
isc::log::Logger logger
Definition: init.cpp:28
std::basic_string< std::byte > byte_string
Definition: bytea_utils.hpp:17
std::vector< uint8_t > to_bytea(byte_string_view str)
Convert a byte string to a byte array.
Definition: bytea_utils.cpp:18
byte_string to_bytestr(const std::vector< uint8_t > &bytea)
Convert a byte array to a byte string.
Definition: bytea_utils.cpp:7
pqxx::binarystring to_binstr(const std::vector< std::uint8_t > &bytea)
Convert a byte array to a binary string.
Definition: pqxx_compat.cpp:8
Compatibility macros and utilities for libpqxx < 7.
pqxx::binarystring bytestr_t
Definition: pqxx_compat.hpp:22
#define STMT_NAME_T
Definition: pqxx_compat.hpp:24
Type type
Whether the given host reservation shall be added or removed.
isc::asiolink::IOAddress ipv4
static AdminDBClient::HostUpdate make_host_update_from_hosts_row(const pqxx::row &row)
static AdminDBClient::HostUpdate make_host_update_from_diff_row(const pqxx::row &row)
LocalDBClientPrivate(const LocalDBClientPrivate &)=delete
auto compare_current_and_tmp_host_reservations(pqxx::work &transaction)
void insert_tmp_host_reservations(pqxx::work &transaction, const std::vector< AdminDBClient::HostUpdate > &full_sync)
static void remove_tmp_host_table(pqxx::work &transaction)
void apply_host_update(pqxx::work &transaction, const AdminDBClient::HostUpdate &host_update)
LocalDBClientPrivate(const std::string &connstr)