Kea  2.1.7-git
ha_config.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 <asiolink/io_address.h>
10 #include <asiolink/io_error.h>
11 #include <asiolink/crypto_tls.h>
12 #include <dhcpsrv/cfgmgr.h>
14 #include <exceptions/exceptions.h>
16 #include <util/strutil.h>
17 #include <ha_log.h>
18 #include <ha_config.h>
19 #include <ha_service_states.h>
20 #include <sstream>
21 
22 using namespace isc::asiolink;
23 using namespace isc::http;
24 using namespace isc::util;
25 using namespace isc::dhcp;
26 
27 namespace isc {
28 namespace ha {
29 
30 HAConfig::PeerConfig::PeerConfig()
31  : tls_context_(), name_(), url_(""), trust_anchor_(), cert_file_(),
32  key_file_(), role_(STANDBY), auto_failover_(false), basic_auth_() {
33 }
34 
35 void
36 HAConfig::PeerConfig::setName(const std::string& name) {
37  // We want to make sure that someone didn't provide a name that consists of
38  // a single space, tab etc.
39  const std::string& s = util::str::trim(name);
40  if (s.empty()) {
41  isc_throw(BadValue, "peer name must not be empty");
42  }
43  name_ = s;
44 }
45 
46 void
47 HAConfig::PeerConfig::setRole(const std::string& role) {
48  role_ = stringToRole(role);
49 }
50 
51 std::string
53  std::ostringstream label;
54  label << getName() << " (" << getUrl().toText() << ")";
55  return (label.str());
56 }
57 
59 HAConfig::PeerConfig::stringToRole(const std::string& role) {
60  if (role == "primary") {
62 
63  } else if (role == "secondary") {
65 
66  } else if (role == "standby") {
68 
69  } else if (role == "backup") {
71 
72  }
73 
74  // Invalid value specified.
75  isc_throw(BadValue, "unsupported value '" << role << "' for role parameter");
76 }
77 
78 std::string
80  switch (role) {
82  return ("primary");
84  return ("secondary");
86  return ("standby");
88  return ("backup");
89  default:
90  ;
91  }
92  return ("");
93 }
94 
95 void
97  const BasicHttpAuthPtr& auth = getBasicAuth();
98  if (!request || !auth) {
99  return;
100  }
101  request->context()->headers_.push_back(BasicAuthHttpHeaderContext(*auth));
102 }
103 
105  : state_(state), pausing_(STATE_PAUSE_NEVER) {
106 }
107 
108 void
109 HAConfig::StateConfig::setPausing(const std::string& pausing) {
110  pausing_ = stringToPausing(pausing);
111 }
112 
114 HAConfig::StateConfig::stringToPausing(const std::string& pausing) {
115  if (pausing == "always") {
116  return (STATE_PAUSE_ALWAYS);
117 
118  } else if (pausing == "never") {
119  return (STATE_PAUSE_NEVER);
120 
121  } else if (pausing == "once") {
122  return (STATE_PAUSE_ONCE);
123  }
124 
125  isc_throw(BadValue, "unsupported value " << pausing << " of 'pause' parameter");
126 }
127 
128 std::string
130  switch (pausing) {
131  case STATE_PAUSE_ALWAYS:
132  return ("always");
133 
134  case STATE_PAUSE_NEVER:
135  return ("never");
136 
137  case STATE_PAUSE_ONCE:
138  return ("once");
139 
140  default:
141  ;
142  }
143 
144  isc_throw(BadValue, "unsupported pause enumeration " << static_cast<int>(pausing));
145 }
146 
149  // Return config for the state if it exists already.
150  auto state_config = states_.find(state);
151  if (state_config != states_.end()) {
152  return (state_config->second);
153  }
154 
155  // Create config for the state and store its pointer.
156  StateConfigPtr new_state_config(new StateConfig(state));
157  states_[state] = new_state_config;
158 
159  return (new_state_config);
160 }
161 
164  sync_leases_(true), sync_timeout_(60000), sync_page_limit_(10000),
170  restrict_commands_(false), peers_(),
172 }
173 
175 HAConfig::selectNextPeerConfig(const std::string& name) {
176  // Check if there is a configuration for this server name already. We can't
177  // have two servers with the same name.
178  if (peers_.count(name) > 0) {
179  isc_throw(BadValue, "peer with name '" << name << "' already specified");
180  }
181 
182  // Name appears to be unique, so let's add it.
183  PeerConfigPtr cfg(new PeerConfig());
184  cfg->setName(name);
185  peers_[name] = cfg;
186 
187  // Return this to the caller so as the caller can set parsed configuration
188  // for this peer.
189  return (cfg);
190 }
191 
192 void
193 HAConfig::setThisServerName(const std::string& this_server_name) {
194  // Avoid names consisting of spaces, tabs etc.
195  std::string s = util::str::trim(this_server_name);
196  if (s.empty()) {
197  isc_throw(BadValue, "'this-server-name' value must not be empty");
198  }
199 
200  this_server_name_ = s;
201 }
202 
203 
204 void
205 HAConfig::setHAMode(const std::string& ha_mode) {
206  ha_mode_ = stringToHAMode(ha_mode);
207 }
208 
210 HAConfig::stringToHAMode(const std::string& ha_mode) {
211  if (ha_mode == "load-balancing") {
212  return (LOAD_BALANCING);
213 
214  } else if (ha_mode == "hot-standby") {
215  return (HOT_STANDBY);
216 
217  } else if (ha_mode == "passive-backup") {
218  return (PASSIVE_BACKUP);
219  }
220 
221  isc_throw(BadValue, "unsupported value '" << ha_mode << "' for mode parameter");
222 }
223 
224 std::string
226  switch (ha_mode) {
227  case LOAD_BALANCING:
228  return ("load-balancing");
229  case HOT_STANDBY:
230  return ("hot-standby");
231  case PASSIVE_BACKUP:
232  return ("passive-backup");
233  default:
234  ;
235  }
236  return ("");
237 }
238 
240 HAConfig::getPeerConfig(const std::string& name) const {
241  auto peer = peers_.find(name);
242  if (peer == peers_.end()) {
243  isc_throw(InvalidOperation, "no configuration specified for server " << name);
244  }
245 
246  return (peer->second);
247 }
248 
252  for (auto peer = servers.begin(); peer != servers.end(); ++peer) {
253  if (peer->second->getRole() != HAConfig::PeerConfig::BACKUP) {
254  return (peer->second);
255  }
256  }
257 
258  isc_throw(InvalidOperation, "no failover partner server found for this"
259  " server " << getThisServerName());
260 }
261 
264  return (getPeerConfig(getThisServerName()));
265 }
266 
270  copy.erase(getThisServerName());
271  return (copy);
272 }
273 
274 void
276  // Peers configurations must be provided.
277  if (peers_.count(getThisServerName()) == 0) {
278  isc_throw(HAConfigValidationError, "no peer configuration specified for the '"
279  << getThisServerName() << "'");
280  }
281 
282  // Gather all the roles and see how many occurrences of each role we get.
283  std::map<PeerConfig::Role, unsigned> peers_cnt;
284  for (auto p = peers_.begin(); p != peers_.end(); ++p) {
285  if (!p->second->getUrl().isValid()) {
286  isc_throw(HAConfigValidationError, "invalid URL: "
287  << p->second->getUrl().getErrorMessage()
288  << " for server " << p->second->getName());
289  }
290 
291  // The hostname must be an address, not a name.
292  IOAddress addr("::");
293  try {
294  addr = IOAddress(p->second->getUrl().getStrippedHostname());
295  } catch (const IOError& ex) {
297  << p->second->getUrl().toText()
298  << "': " << ex.what()
299  << " for server " << p->second->getName());
300  }
301 
302  // Check TLS setup.
303  Optional<std::string> ca = p->second->getTrustAnchor();
304  Optional<std::string> cert = p->second->getCertFile();
305  Optional<std::string> key = p->second->getKeyFile();
306  // When not configured get the value from the global level.
307  if (ca.unspecified()) {
308  ca = trust_anchor_;
309  }
310  if (cert.unspecified()) {
311  cert = cert_file_;
312  }
313  if (key.unspecified()) {
314  key = key_file_;
315  }
316  bool have_ca = (!ca.unspecified() && !ca.get().empty());
317  bool have_cert = (!cert.unspecified() && !cert.get().empty());
318  bool have_key = (!key.unspecified() && !key.get().empty());
319  bool use_tls = (have_ca || have_cert || have_key);
320  if (use_tls) {
321  try {
322  // TLS is used: all 3 parameters are required.
323  if (!have_ca) {
324  isc_throw(HAConfigValidationError, "trust-anchor parameter"
325  << " is missing or empty: all or none of"
326  << " TLS parameters must be set");
327  }
328  if (!have_cert) {
329  isc_throw(HAConfigValidationError, "cert-file parameter"
330  << " is missing or empty: all or none of"
331  << " TLS parameters must be set");
332  }
333  if (!have_key) {
334  isc_throw(HAConfigValidationError, "key-file parameter"
335  << " is missing or empty: all or none of"
336  << " TLS parameters must be set");
337  }
338  TlsRole tls_role = TlsRole::CLIENT;
339  bool cert_required = true;
340  // The peer entry for myself will be used for the server side.
341  if (p->second->getName() == getThisServerName()) {
342  tls_role = TlsRole::SERVER;
343  cert_required = getRequireClientCerts();
344  }
345  TlsContext::configure(p->second->tls_context_,
346  tls_role,
347  ca.get(),
348  cert.get(),
349  key.get(),
350  cert_required);
351  } catch (const isc::Exception& ex) {
352  isc_throw(HAConfigValidationError, "bad TLS config for server "
353  << p->second->getName() << ": " << ex.what());
354  }
355  } else {
356  // Refuse HTTPS scheme when TLS is not enabled.
357  if (p->second->getUrl().getScheme() == Url::HTTPS) {
359  << p->second->getUrl().toText()
360  << "': https scheme is not supported"
361  << " for server " << p->second->getName()
362  << " where TLS is disabled");
363  }
364  }
365 
366  ++peers_cnt[p->second->getRole()];
367  }
368 
369  // Only one primary server allowed.
370  if (peers_cnt.count(PeerConfig::PRIMARY) && (peers_cnt[PeerConfig::PRIMARY] > 1)) {
371  isc_throw(HAConfigValidationError, "multiple primary servers specified");
372  }
373 
374  // Only one secondary server allowed.
375  if (peers_cnt.count(PeerConfig::SECONDARY) && (peers_cnt[PeerConfig::SECONDARY] > 1)) {
376  isc_throw(HAConfigValidationError, "multiple secondary servers specified");
377  }
378 
379  // Only one standby server allowed.
380  if (peers_cnt.count(PeerConfig::STANDBY) && (peers_cnt[PeerConfig::STANDBY] > 1)) {
381  isc_throw(HAConfigValidationError, "multiple standby servers specified");
382  }
383 
384  if (ha_mode_ == LOAD_BALANCING) {
385  // Standby servers not allowed in load balancing configuration.
386  if (peers_cnt.count(PeerConfig::STANDBY) > 0) {
387  isc_throw(HAConfigValidationError, "standby servers not allowed in the load "
388  "balancing configuration");
389  }
390 
391  // Require one secondary server in the load balancing configuration.
392  if (peers_cnt.count(PeerConfig::SECONDARY) == 0) {
393  isc_throw(HAConfigValidationError, "secondary server required in the load"
394  " balancing configuration");
395  }
396 
397  // Require one primary server in the load balancing configuration.
398  if (peers_cnt.count(PeerConfig::PRIMARY) == 0) {
399  isc_throw(HAConfigValidationError, "primary server required in the load"
400  " balancing configuration");
401  }
402 
403  // In the load-balancing mode the wait-backup-ack must be false.
404  if (wait_backup_ack_) {
405  isc_throw(HAConfigValidationError, "'wait-backup-ack' must be set to false in the"
406  " load balancing configuration");
407  }
408 
409  } else if (ha_mode_ == HOT_STANDBY) {
410  // Secondary servers not allowed in the hot standby configuration.
411  if (peers_cnt.count(PeerConfig::SECONDARY) > 0) {
412  isc_throw(HAConfigValidationError, "secondary servers not allowed in the hot"
413  " standby configuration");
414  }
415 
416  // Require one standby server in the hot standby configuration.
417  if (peers_cnt.count(PeerConfig::STANDBY) == 0) {
418  isc_throw(HAConfigValidationError, "standby server required in the hot"
419  " standby configuration");
420  }
421 
422  // Require one primary server in the hot standby configuration.
423  if (peers_cnt.count(PeerConfig::PRIMARY) == 0) {
424  isc_throw(HAConfigValidationError, "primary server required in the hot"
425  " standby configuration");
426  }
427 
428  // In the hot-standby mode the wait-backup-ack must be false.
429  if (wait_backup_ack_) {
430  isc_throw(HAConfigValidationError, "'wait-backup-ack' must be set to false in the"
431  " hot standby configuration");
432  }
433 
434  // The server must not transition to communication-recovery state in
435  // hot-standby mode.
436  if (delayed_updates_limit_ > 0) {
437  isc_throw(HAConfigValidationError, "'delayed-updates-limit' must be set to 0 in"
438  " the hot standby configuration");
439  }
440 
441  } else if (ha_mode_ == PASSIVE_BACKUP) {
442  if (peers_cnt.count(PeerConfig::SECONDARY) > 0) {
443  isc_throw(HAConfigValidationError, "secondary servers not allowed in the"
444  " passive backup configuration");
445  }
446 
447  if (peers_cnt.count(PeerConfig::STANDBY) > 0) {
448  isc_throw(HAConfigValidationError, "standby servers not allowed in the"
449  " passive backup configuration");
450  }
451 
452  if (peers_cnt.count(PeerConfig::PRIMARY) == 0) {
453  isc_throw(HAConfigValidationError, "primary server required in the"
454  " passive backup configuration");
455  }
456 
457  // The server must not transition to communication-recovery state in
458  // passive-backup mode.
459  if (delayed_updates_limit_ > 0) {
460  isc_throw(HAConfigValidationError, "'delayed-updates-limit' must be set to 0 in"
461  " the passive backup configuration");
462  }
463  }
464 
466  // We get it from staging because applying the DHCP multi-threading configuration
467  // occurs after library loading during the (re)configuration process.
468  auto mcfg = CfgMgr::instance().getStagingCfg()->getDHCPMultiThreading();
469  bool dhcp_mt_enabled = false;
470  uint32_t dhcp_threads = 0;
471  uint32_t dummy_queue_size = 0;
472  CfgMultiThreading::extract(mcfg, dhcp_mt_enabled, dhcp_threads, dummy_queue_size);
473 
474  if (!dhcp_mt_enabled) {
475  // HA+MT requires DHCP multi-threading.
477  enable_multi_threading_ = false;
478  return;
479  }
480 
481  // When DHCP threads is configured as zero, we should auto-detect.
482  if (!dhcp_threads) {
483  dhcp_threads = MultiThreadingMgr::detectThreadCount();
484  // If machine says it cannot support threads.
485  if (!dhcp_threads) {
487  enable_multi_threading_ = false;
488  return;
489  }
490  }
491 
492  // If http_listener_threads_ is 0, then we use the same number of
493  // threads as DHCP does.
494  if (http_listener_threads_ == 0) {
495  http_listener_threads_ = dhcp_threads;
496  }
497 
498  // If http_client_threads_ is 0, then we use the same number of
499  // threads as DHCP does.
500  if (http_client_threads_ == 0) {
501  http_client_threads_ = dhcp_threads;
502  }
503  }
504 }
505 
506 } // end of namespace isc::ha
507 } // end of namespace isc
PeerConfigPtr getPeerConfig(const std::string &name) const
Returns configuration of the specified server.
Definition: ha_config.cc:240
uint32_t max_unacked_clients_
Maximum number of unacked clients.
Definition: ha_config.h:770
void unspecified(bool unspecified)
Modifies the flag that indicates whether the value is specified or unspecified.
Definition: optional.h:136
StatePausing
State machine pausing modes.
Definition: state_model.h:45
PeerConfigMap peers_
Map of peers&#39; configurations.
Definition: ha_config.h:781
bool getRequireClientCerts() const
Returns require-client-certs.
Definition: ha_config.h:672
void setThisServerName(const std::string &this_server_name)
Sets name of this server.
Definition: ha_config.cc:193
void setName(const std::string &name)
Sets server name.
Definition: ha_config.cc:36
State machine configuration information.
Definition: ha_config.h:287
void setHAMode(const std::string &ha_mode)
Sets new mode of operation.
Definition: ha_config.cc:205
bool enable_multi_threading_
Enable multi-threading.
Definition: ha_config.h:772
void setPausing(const std::string &pausing)
Sets pausing mode for the given state.
Definition: ha_config.cc:109
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
std::map< std::string, PeerConfigPtr > PeerConfigMap
Map of the servers&#39; configurations.
Definition: ha_config.h:232
static Role stringToRole(const std::string &role)
Decodes role provided as a string.
Definition: ha_config.cc:59
std::string this_server_name_
This server name.
Definition: ha_config.h:758
util::Optional< std::string > key_file_
Private key file.
Definition: ha_config.h:778
StateConfigPtr getStateConfig(const int state)
Returns pointer to the state specific configuration.
Definition: ha_config.cc:148
StateConfig(const int state)
Constructor.
Definition: ha_config.cc:104
T get() const
Retrieves the encapsulated value.
Definition: optional.h:114
PeerConfigPtr getFailoverPeerConfig() const
Returns configuration of the partner which takes part in failover.
Definition: ha_config.cc:250
util::Optional< std::string > cert_file_
Certificate file.
Definition: ha_config.h:777
HAMode
Mode of operation.
Definition: ha_config.h:42
uint32_t max_response_delay_
Max delay in response to heartbeats.
Definition: ha_config.h:768
Represents basic HTTP authentication header.
Definition: basic_auth.h:73
bool sync_leases_
Synchronize databases on startup?
Definition: ha_config.h:761
bool http_dedicated_listener_
Enable use of own HTTP listener.
Definition: ha_config.h:773
bool wait_backup_ack_
Wait for lease update ack from backup?
Definition: ha_config.h:771
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
util::Optional< std::string > trust_anchor_
Trust anchor.
Definition: ha_config.h:776
StateMachineConfigPtr state_machine_
State machine configuration.
Definition: ha_config.h:782
http::BasicHttpAuthPtr & getBasicAuth()
Returns non-const basic HTTP authentication.
Definition: ha_config.h:197
uint32_t sync_page_limit_
Page size limit while synchronizing leases.
Definition: ha_config.h:763
uint32_t heartbeat_delay_
Heartbeat delay in milliseconds.
Definition: ha_config.h:767
void addBasicAuthHttpHeader(http::PostHttpRequestJsonPtr request) const
Adds a basic HTTP authentication header to a request when credentials are specified.
Definition: ha_config.cc:96
uint32_t max_ack_delay_
Maximum DHCP message ack delay.
Definition: ha_config.h:769
std::string getLogLabel() const
Returns a string identifying a server used in logging.
Definition: ha_config.cc:52
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Exception thrown when configuration validation fails.
Definition: ha_config.h:26
Definition: edns.h:19
boost::shared_ptr< PostHttpRequestJson > PostHttpRequestJsonPtr
Pointer to PostHttpRequestJson.
std::string getThisServerName() const
Returns name of this server.
Definition: ha_config.h:330
uint32_t sync_timeout_
Timeout for syncing lease database (ms)
Definition: ha_config.h:762
ElementPtr copy(ConstElementPtr from, int level)
Copy the data up to a nesting level.
Definition: data.cc:1152
HAConfig()
Constructor.
Definition: ha_config.cc:162
bool require_client_certs_
Require client certs flag.
Definition: ha_config.h:779
boost::shared_ptr< StateConfig > StateConfigPtr
Pointer to the state configuration.
Definition: ha_config.h:280
bool restrict_commands_
Restrict commands to HA flag.
Definition: ha_config.h:780
bool send_lease_updates_
Send lease updates to partner?
Definition: ha_config.h:760
const isc::log::MessageID HA_CONFIG_DHCP_MT_DISABLED
Definition: ha_messages.h:28
std::string toText() const
Returns textual representation of the URL.
Definition: url.cc:65
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-lfc.
TLS API.
PeerConfigMap getOtherServersConfig() const
Returns configuration of other servers.
Definition: ha_config.cc:268
static std::string HAModeToString(const HAMode &ha_mode)
Returns HA mode name.
Definition: ha_config.cc:225
const Name & name_
Definition: dns/message.cc:693
void setRole(const std::string &role)
Sets servers role.
Definition: ha_config.cc:47
A generic exception that is thrown if a function is called in a prohibited way.
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
Role
Server&#39;s role in the High Availability setup.
Definition: ha_config.h:70
static std::string pausingToString(const util::StatePausing &pausing)
Returns pausing mode in the textual form.
Definition: ha_config.cc:129
uint32_t delayed_updates_limit_
Maximum number of lease updates held for later send in communication-recovery.
Definition: ha_config.h:765
string trim(const string &instring)
Trim Leading and Trailing Spaces.
Definition: strutil.cc:53
static std::string roleToString(const HAConfig::PeerConfig::Role &role)
Returns role name.
Definition: ha_config.cc:79
PeerConfigPtr getThisServerConfig() const
Returns configuration of this server.
Definition: ha_config.cc:263
PeerConfigPtr selectNextPeerConfig(const std::string &name)
Creates and returns pointer to the new peer&#39;s configuration.
Definition: ha_config.cc:175
std::string getName() const
Returns server name.
Definition: ha_config.h:81
static util::StatePausing stringToPausing(const std::string &pausing)
Converts pausing mode from the textual form.
Definition: ha_config.cc:114
uint32_t http_listener_threads_
Number of HTTP listener threads.
Definition: ha_config.h:774
void validate()
Validates configuration.
Definition: ha_config.cc:275
const isc::log::MessageID HA_CONFIG_SYSTEM_MT_UNSUPPORTED
Definition: ha_messages.h:34
uint32_t http_client_threads_
Number of HTTP client threads.
Definition: ha_config.h:775
http::Url getUrl() const
Returns URL of the server&#39;s control channel.
Definition: ha_config.h:92
static HAMode stringToHAMode(const std::string &ha_mode)
Decodes HA mode provided as string.
Definition: ha_config.cc:210
HAMode ha_mode_
Mode of operation.
Definition: ha_config.h:759
boost::shared_ptr< PeerConfig > PeerConfigPtr
Pointer to the server&#39;s configuration.
Definition: ha_config.h:229
HA peer configuration.
Definition: ha_config.h:53