Kea 2.7.1
ha_config_parser.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2024 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
7#include <config.h>
8
9#include <ha_config_parser.h>
10#include <ha_log.h>
11#include <ha_service_states.h>
13#include <util/filesystem.h>
14#include <boost/make_shared.hpp>
15#include <limits>
16#include <set>
17
18using namespace isc::data;
19using namespace isc::http;
20
21namespace {
22
24const SimpleDefaults HA_CONFIG_LB_DEFAULTS = {
25 { "delayed-updates-limit", Element::integer, "100" },
26};
27
29const SimpleDefaults HA_CONFIG_DEFAULTS = {
30 { "delayed-updates-limit", Element::integer, "0" },
31 { "heartbeat-delay", Element::integer, "10000" },
32 { "max-ack-delay", Element::integer, "10000" },
33 { "max-response-delay", Element::integer, "60000" },
34 { "max-unacked-clients", Element::integer, "10" },
35 { "max-rejected-lease-updates", Element::integer, "10" },
36 { "require-client-certs", Element::boolean, "true" },
37 { "restrict-commands", Element::boolean, "false" },
38 { "send-lease-updates", Element::boolean, "true" },
39 { "sync-leases", Element::boolean, "true" },
40 { "sync-timeout", Element::integer, "60000" },
41 { "sync-page-limit", Element::integer, "10000" },
42 { "wait-backup-ack", Element::boolean, "false" }
43};
44
46const SimpleDefaults HA_CONFIG_MT_DEFAULTS = {
47 { "enable-multi-threading", Element::boolean, "true" },
48 { "http-client-threads", Element::integer, "0" },
49 { "http-dedicated-listener", Element::boolean, "true" },
50 { "http-listener-threads", Element::integer, "0" }
51};
52
54const SimpleDefaults HA_CONFIG_PEER_DEFAULTS = {
55 { "auto-failover", Element::boolean, "true" }
56};
57
59const SimpleDefaults HA_CONFIG_STATE_DEFAULTS = {
60 { "pause", Element::string, "never" }
61};
62
63} // end of anonymous namespace
64
65namespace isc {
66namespace ha {
67
70 try {
71 auto config_storage = boost::make_shared<HAConfigMapper>();
72
73 // This may cause different types of exceptions. We catch them here
74 // and throw unified exception type.
75 parseAll(config_storage, config);
76 validateRelationships(config_storage);
77 logConfigStatus(config_storage);
78 return (config_storage);
79
80 } catch (const ConfigError& ex) {
81 throw;
82
83 } catch (const std::exception& ex) {
85 }
86}
87
88void
89HAConfigParser::parseAll(const HAConfigMapperPtr& config_storage,
90 const ConstElementPtr& config) {
91 // Config must be provided.
92 if (!config) {
93 isc_throw(ConfigError, "HA configuration must not be null");
94 }
95
96 // Config must be a list. Each contains one relationship between servers in the
97 // HA configuration. Currently we support only one relationship.
98 if (config->getType() != Element::list) {
99 isc_throw(ConfigError, "HA configuration must be a list");
100 }
101
102 // Get the HA configuration.
103 auto const& config_vec = config->listValue();
104 if (config_vec.empty()) {
105 isc_throw(ConfigError, "a list of HA configurations must not be empty");
106 }
107 for (auto const& cfg : config_vec) {
108 parseOne(config_storage, cfg);
109 }
110}
111
112void
113HAConfigParser::parseOne(const HAConfigMapperPtr& config_storage,
114 const ElementPtr& config) {
115 // Config must be provided.
116 if (!config) {
117 isc_throw(ConfigError, "HA configuration must not be null");
118 }
119
120 // Config must be a map.
121 if (config->getType() != Element::map) {
122 isc_throw(ConfigError, "HA configuration for a relationship must be a map");
123 }
124
125 auto rel_config = HAConfig::create();
126
127 // Get 'mode'. That's the first thing to gather because the defaults we
128 // apply to the configuration depend on the mode.
129 rel_config->setHAMode(getString(config, "mode"));
130
131 // Set load-balancing specific defaults.
132 if (rel_config->getHAMode() == HAConfig::LOAD_BALANCING) {
133 setDefaults(config, HA_CONFIG_LB_DEFAULTS);
134 }
135 // Set general defaults.
136 setDefaults(config, HA_CONFIG_DEFAULTS);
137
138 // It must contain peers section.
139 if (!config->contains("peers")) {
140 isc_throw(ConfigError, "'peers' parameter missing in HA configuration");
141 }
142
143 // Peers configuration must be a list of maps.
144 ConstElementPtr peers = config->get("peers");
145 if (peers->getType() != Element::list) {
146 isc_throw(ConfigError, "'peers' parameter must be a list");
147 }
148
149 // State machine configuration must be a map.
150 ConstElementPtr state_machine = config->get("state-machine");
151 ConstElementPtr states_list;
152 if (state_machine) {
153 if (state_machine->getType() != Element::map) {
154 isc_throw(ConfigError, "'state-machine' parameter must be a map");
155 }
156
157 states_list = state_machine->get("states");
158 if (states_list && (states_list->getType() != Element::list)) {
159 isc_throw(ConfigError, "'states' parameter must be a list");
160 }
161 }
162
163 // We have made major sanity checks, so let's try to gather some values.
164
165 // Get 'this-server-name'.
166 rel_config->setThisServerName(getString(config, "this-server-name"));
167
168 // Get 'send-lease-updates'.
169 rel_config->setSendLeaseUpdates(getBoolean(config, "send-lease-updates"));
170
171 // Get 'sync-leases'.
172 rel_config->setSyncLeases(getBoolean(config, "sync-leases"));
173
174 // Get 'sync-timeout'.
175 uint32_t sync_timeout = getAndValidateInteger<uint32_t>(config, "sync-timeout");
176 rel_config->setSyncTimeout(sync_timeout);
177
178 // Get 'sync-page-limit'.
179 uint32_t sync_page_limit = getAndValidateInteger<uint32_t>(config, "sync-page-limit");
180 rel_config->setSyncPageLimit(sync_page_limit);
181
182 // Get 'delayed-updates-limit'.
183 uint32_t delayed_updates_limit = getAndValidateInteger<uint32_t>(config, "delayed-updates-limit");
184 rel_config->setDelayedUpdatesLimit(delayed_updates_limit);
185
186 // Get 'heartbeat-delay'.
187 uint16_t heartbeat_delay = getAndValidateInteger<uint16_t>(config, "heartbeat-delay");
188 rel_config->setHeartbeatDelay(heartbeat_delay);
189
190 // Get 'max-response-delay'.
191 uint16_t max_response_delay = getAndValidateInteger<uint16_t>(config, "max-response-delay");
192 rel_config->setMaxResponseDelay(max_response_delay);
193
194 // Get 'max-ack-delay'.
195 uint16_t max_ack_delay = getAndValidateInteger<uint16_t>(config, "max-ack-delay");
196 rel_config->setMaxAckDelay(max_ack_delay);
197
198 // Get 'max-unacked-clients'.
199 uint32_t max_unacked_clients = getAndValidateInteger<uint32_t>(config, "max-unacked-clients");
200 rel_config->setMaxUnackedClients(max_unacked_clients);
201
202 // Get 'max-rejected-lease-updates'.
203 uint32_t max_rejected_lease_updates = getAndValidateInteger<uint32_t>(config, "max-rejected-lease-updates");
204 rel_config->setMaxRejectedLeaseUpdates(max_rejected_lease_updates);
205
206 // Get 'wait-backup-ack'.
207 rel_config->setWaitBackupAck(getBoolean(config, "wait-backup-ack"));
208
209 // Get multi-threading map.
210 ElementPtr mt_config = boost::const_pointer_cast<Element>(config->get("multi-threading"));
211 if (!mt_config) {
212 // Not there, make an empty one.
213 mt_config = Element::createMap();
214 config->set("multi-threading", mt_config);
215 } else if (mt_config->getType() != Element::map) {
216 isc_throw(ConfigError, "multi-threading configuration must be a map");
217 }
218
219 // Backfill the MT defaults.
220 setDefaults(mt_config, HA_CONFIG_MT_DEFAULTS);
221
222 // Get 'enable-multi-threading'.
223 rel_config->setEnableMultiThreading(getBoolean(mt_config, "enable-multi-threading"));
224
225 // Get 'http-dedicated-listener'.
226 rel_config->setHttpDedicatedListener(getBoolean(mt_config, "http-dedicated-listener"));
227
228 // Get 'http-listener-threads'.
229 uint32_t threads = getAndValidateInteger<uint32_t>(mt_config, "http-listener-threads");
230 rel_config->setHttpListenerThreads(threads);
231
232 // Get 'http-client-threads'.
233 threads = getAndValidateInteger<uint32_t>(mt_config, "http-client-threads");
234 rel_config->setHttpClientThreads(threads);
235
236 // Get optional 'trust-anchor'.
237 ConstElementPtr ca = config->get("trust-anchor");
238 if (ca) {
239 rel_config->setTrustAnchor(getString(config, "trust-anchor"));
240 }
241
242 // Get optional 'cert-file'.
243 ConstElementPtr cert = config->get("cert-file");
244 if (cert) {
245 rel_config->setCertFile(getString(config, "cert-file"));
246 }
247
248 // Get optional 'key-file'.
249 ConstElementPtr key = config->get("key-file");
250 if (key) {
251 rel_config->setKeyFile(getString(config, "key-file"));
252 }
253
254 // Get 'require-client-certs'.
255 rel_config->setRequireClientCerts(getBoolean(config, "require-client-certs"));
256
257 // Get 'restrict-commands'.
258 rel_config->setRestrictCommands(getBoolean(config, "restrict-commands"));
259
260 // Peers configuration parsing.
261 auto const& peers_vec = peers->listValue();
262
263 // Go over configuration of each peer.
264 for (auto const& p : peers_vec) {
265
266 // Peer configuration is held in a map.
267 if (p->getType() != Element::map) {
268 isc_throw(ConfigError, "peer configuration must be a map");
269 }
270
271 setDefaults(p, HA_CONFIG_PEER_DEFAULTS);
272
273 // Server name.
274 auto cfg = rel_config->selectNextPeerConfig(getString(p, "name"));
275
276 // URL.
277 cfg->setUrl(Url(getString(p, "url")));
278
279 // Optional trust anchor.
280 if (p->contains("trust-anchor")) {
281 cfg->setTrustAnchor(getString(p, ("trust-anchor")));
282 }
283
284 // Optional certificate file.
285 if (p->contains("cert-file")) {
286 cfg->setCertFile(getString(p, ("cert-file")));
287 }
288
289 // Optional private key file.
290 if (p->contains("key-file")) {
291 cfg->setKeyFile(getString(p, ("key-file")));
292 }
293
294 // Role.
295 cfg->setRole(getString(p, "role"));
296
297 // Auto failover configuration.
298 cfg->setAutoFailover(getBoolean(p, "auto-failover"));
299
300 // Basic HTTP authentication password.
301 std::string password;
302 if (p->contains("basic-auth-password")) {
303 if (p->contains("basic-auth-password-file")) {
304 isc_throw(dhcp::DhcpConfigError, "only one of "
305 << "basic-auth-password and "
306 << "basic-auth-password-file parameter can be "
307 << "configured in peer '"
308 << cfg->getName() << "'");
309 }
310 password = getString(p, "basic-auth-password");
311 }
312 if (p->contains("basic-auth-password-file")) {
313 std::string password_file =
314 getString(p, "basic-auth-password-file");
315 try {
316 password = util::file::getContent(password_file);
317 } catch (const std::exception& ex) {
318 isc_throw(dhcp::DhcpConfigError, "bad password file in peer '"
319 << cfg->getName() << "': " << ex.what());
320 }
321 }
322
323 // Basic HTTP authentication user.
324 if (p->contains("basic-auth-user")) {
325 std::string user = getString(p, "basic-auth-user");
326 BasicHttpAuthPtr& auth = cfg->getBasicAuth();
327 try {
328 if (!user.empty()) {
329 // Validate the user id value.
330 auth.reset(new BasicHttpAuth(user, password));
331 }
332 } catch (const std::exception& ex) {
333 isc_throw(dhcp::DhcpConfigError, ex.what() << " in peer '"
334 << cfg->getName() << "'");
335 }
336 }
337 }
338
339 // Per state configuration is optional.
340 if (states_list) {
341 auto const& states_vec = states_list->listValue();
342
343 std::set<int> configured_states;
344
345 // Go over per state configurations.
346 for (auto const& s : states_vec) {
347
348 // State configuration is held in map.
349 if (s->getType() != Element::map) {
350 isc_throw(ConfigError, "state configuration must be a map");
351 }
352
353 setDefaults(s, HA_CONFIG_STATE_DEFAULTS);
354
355 // Get state name and set per state configuration.
356 std::string state_name = getString(s, "state");
357
358 int state = stringToState(state_name);
359 // Check that this configuration doesn't duplicate existing configuration.
360 if (configured_states.count(state) > 0) {
361 isc_throw(ConfigError, "duplicated configuration for the '"
362 << state_name << "' state");
363 }
364 configured_states.insert(state);
365
366 rel_config->getStateMachineConfig()->
367 getStateConfig(state)->setPausing(getString(s, "pause"));
368 }
369 }
370
371 // We have gone over the entire configuration and stored it in the configuration
372 // storage. However, we need to still validate it to detect errors like:
373 // duplicate secondary/primary servers, no configuration for this server etc.
374 rel_config->validate();
375
376 auto peer_configs = rel_config->getAllServersConfig();
377 for (auto const& peer_config : peer_configs) {
378 try {
379 config_storage->map(peer_config.first, rel_config);
380
381 } catch (const std::exception& ex) {
382 isc_throw(HAConfigValidationError, "server names must be unique for different relationships: "
383 << ex.what());
384 }
385 }
386}
387
388template<typename T>
389T HAConfigParser::getAndValidateInteger(const ConstElementPtr& config,
390 const std::string& parameter_name) {
391 int64_t value = getInteger(config, parameter_name);
392 if (value < 0) {
393 isc_throw(ConfigError, "'" << parameter_name << "' must not be negative");
394
395 } else if (value > std::numeric_limits<T>::max()) {
396 isc_throw(ConfigError, "'" << parameter_name << "' must not be greater than "
397 << +std::numeric_limits<T>::max());
398 }
399
400 return (static_cast<T>(value));
401}
402
403void
404HAConfigParser::logConfigStatus(const HAConfigMapperPtr& config_storage) {
406
407 for (auto const& config : config_storage->getAll()) {
408 // If lease updates are disabled, we want to make sure that the user
409 // realizes that and that he has configured some other mechanism to
410 // populate leases.
411 if (!config->amSendingLeaseUpdates()) {
413 .arg(config->getThisServerName());
414 }
415
416 // Same as above but for lease database synchronization.
417 if (!config->amSyncingLeases()) {
419 .arg(config->getThisServerName());
420 }
421
422 // Unusual configuration.
423 if (config->amSendingLeaseUpdates() !=
424 config->amSyncingLeases()) {
426 .arg(config->getThisServerName())
427 .arg(config->amSendingLeaseUpdates() ? "true" : "false")
428 .arg(config->amSyncingLeases() ? "true" : "false");
429 }
430
431 // With this setting the server will not take ownership of the partner's
432 // scope in case of partner's failure. This setting is OK if the
433 // administrator desires to have more control over scopes selection.
434 // The administrator will need to send ha-scopes command to instruct
435 // the server to take ownership of the scope. In some cases he may
436 // also need to send dhcp-enable command to enable DHCP service
437 // (specifically hot-standby mode for standby server).
438 if (!config->getThisServerConfig()->isAutoFailover()) {
440 .arg(config->getThisServerName());
441 }
442 }
443}
444
445void
446HAConfigParser::validateRelationships(const HAConfigMapperPtr& config_storage) {
447 auto configs = config_storage->getAll();
448 if (configs.size() <= 1) {
449 return;
450 }
451 std::unordered_set<std::string> server_names;
452 for (auto const& config : configs) {
453 // Only the hot-standby mode is supported for multiple relationships.
454 if (config->getHAMode() != HAConfig::HOT_STANDBY) {
455 isc_throw(HAConfigValidationError, "multiple HA relationships are only supported for 'hot-standby' mode");
456 }
457 }
458}
459
460} // namespace ha
461} // namespace isc
An exception that is thrown if an error occurs while configuring any server.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:304
static std::string getString(isc::data::ConstElementPtr scope, const std::string &name)
Returns a string parameter from a scope.
static bool getBoolean(isc::data::ConstElementPtr scope, const std::string &name)
Returns a boolean parameter from a scope.
static int64_t getInteger(isc::data::ConstElementPtr scope, const std::string &name)
Returns an integer parameter from a scope.
static size_t setDefaults(isc::data::ElementPtr scope, const SimpleDefaults &default_values)
Sets the default values.
To be removed. Please use ConfigError instead.
static HAConfigMapperPtr parse(const data::ConstElementPtr &config)
Parses HA configuration.
static HAConfigPtr create()
Instantiates a HAConfig.
Definition ha_config.cc:178
Represents a basic HTTP authentication.
Definition basic_auth.h:21
Represents an URL.
Definition url.h:20
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
std::vector< SimpleDefault > SimpleDefaults
This specifies all default values in a given scope (e.g. a subnet).
boost::shared_ptr< Element > ElementPtr
Definition data.h:28
boost::shared_ptr< HAConfigMapper > HAConfigMapperPtr
Pointer to an object mapping HAConfig to relationships.
Definition ha_config.h:43
isc::log::Logger ha_logger("ha-hooks")
Definition ha_log.h:17
const isc::log::MessageID HA_CONFIGURATION_SUCCESSFUL
Definition ha_messages.h:26
const isc::log::MessageID HA_CONFIG_AUTO_FAILOVER_DISABLED
Definition ha_messages.h:27
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_AND_SYNCING_DIFFER
Definition ha_messages.h:32
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_DISABLED
Definition ha_messages.h:33
const isc::log::MessageID HA_CONFIG_LEASE_SYNCING_DISABLED
Definition ha_messages.h:30
int stringToState(const std::string &state_name)
Returns state for a given name.
boost::shared_ptr< BasicHttpAuth > BasicHttpAuthPtr
Type of pointers to basic HTTP authentication objects.
Definition basic_auth.h:70
string getContent(string const &file_name)
Get the content of a regular file.
Definition filesystem.cc:31
Defines the logger used by the top-level component of kea-lfc.