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