Kea  2.1.7-git
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  { "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 
44 const 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 
52 const SimpleDefaults HA_CONFIG_PEER_DEFAULTS = {
53  { "auto-failover", Element::boolean, "true" }
54 };
55 
57 const SimpleDefaults HA_CONFIG_STATE_DEFAULTS = {
58  { "pause", Element::string, "never" }
59 };
60 
61 } // end of anonymous namespace
62 
63 namespace isc {
64 namespace ha {
65 
66 void
67 HAConfigParser::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) {
79  isc_throw(ConfigError, ex.what());
80  }
81 }
82 
83 void
84 HAConfigParser::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 
357 template<typename T>
358 T 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 
372 void
373 HAConfigParser::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
int stringToState(const std::string &state_name)
Returns state for a given name.
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
const isc::log::MessageID HA_CONFIG_LEASE_SYNCING_DISABLED
Definition: ha_messages.h:29
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
std::vector< SimpleDefault > SimpleDefaults
This specifies all default values in a given scope (e.g. a subnet).
Represents a basic HTTP authentication.
Definition: basic_auth.h:21
const isc::log::MessageID HA_CONFIG_AUTO_FAILOVER_DISABLED
Definition: ha_messages.h:27
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_DISABLED
Definition: ha_messages.h:32
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition: data.cc:286
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
An exception that is thrown if an error occurs while configuring any server.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Represents an URL.
Definition: url.h:20
To be removed. Please use ConfigError instead.
string getContent(const string &file_name)
Get the content of a regular file.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
Defines the logger used by the top-level component of kea-lfc.
The Element class represents a piece of data, used by the command channel and configuration parts...
Definition: data.h:70
boost::shared_ptr< BasicHttpAuth > BasicHttpAuthPtr
Type of pointers to basic HTTP authentication objects.
Definition: basic_auth.h:70
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_AND_SYNCING_DIFFER
Definition: ha_messages.h:31
const isc::log::MessageID HA_CONFIGURATION_SUCCESSFUL
Definition: ha_messages.h:26
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition: ha_config.h:786