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