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