summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/high_availability/tests/ha_config_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/dhcp/high_availability/tests/ha_config_unittest.cc')
-rw-r--r--src/hooks/dhcp/high_availability/tests/ha_config_unittest.cc1875
1 files changed, 1875 insertions, 0 deletions
diff --git a/src/hooks/dhcp/high_availability/tests/ha_config_unittest.cc b/src/hooks/dhcp/high_availability/tests/ha_config_unittest.cc
new file mode 100644
index 0000000..5c773a1
--- /dev/null
+++ b/src/hooks/dhcp/high_availability/tests/ha_config_unittest.cc
@@ -0,0 +1,1875 @@
+// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <ha_impl.h>
+#include <ha_service_states.h>
+#include <ha_test.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <config/command_mgr.h>
+#include <util/state_model.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::ha;
+using namespace isc::hooks;
+using namespace isc::ha::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for testing HA hooks library
+/// configuration.
+class HAConfigTest : public HATest {
+public:
+
+ /// @brief Constructor.
+ HAConfigTest()
+ : HATest() {
+ }
+
+ /// @brief Verifies if an exception is thrown if provided HA
+ /// configuration is invalid.
+ ///
+ /// @param invalid_config Configuration to be tested.
+ /// @param expected_error Expected error message.
+ void testInvalidConfig(const std::string& invalid_config,
+ const std::string& expected_error) {
+ HAImplPtr impl(new HAImpl());
+ try {
+ impl->configure(Element::fromJSON(invalid_config));
+ ADD_FAILURE() << "expected ConfigError exception, thrown no exception";
+
+ } catch (const ConfigError& ex) {
+ // Expect the error to be contained in the exception message.
+ std::string const exception(ex.what());
+ EXPECT_NE(exception.find(expected_error), std::string::npos);
+ } catch (...) {
+ ADD_FAILURE() << "expected ConfigError exception, thrown different"
+ " exception type";
+ }
+ }
+};
+
+// Verifies that load balancing configuration is parsed correctly.
+TEST_F(HAConfigTest, configureLoadBalancing) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"send-lease-updates\": false,"
+ " \"sync-leases\": false,"
+ " \"sync-timeout\": 20000,"
+ " \"sync-page-limit\": 3,"
+ " \"delayed-updates-limit\": 111,"
+ " \"heartbeat-delay\": 8,"
+ " \"max-response-delay\": 11,"
+ " \"max-ack-delay\": 5,"
+ " \"max-unacked-clients\": 20,"
+ " \"wait-backup-ack\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"basic-auth-password\": \"1234\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"basic-auth-user\": \"\","
+ " \"role\": \"secondary\""
+ " },"
+ " {"
+ " \"name\": \"server3\","
+ " \"url\": \"http://127.0.0.1:8082/\","
+ " \"basic-auth-user\": \"foo\","
+ " \"basic-auth-password\": \"bar\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": false"
+ " }"
+ " ],"
+ " \"state-machine\": {"
+ " \"states\": ["
+ " {"
+ " \"state\": \"waiting\","
+ " \"pause\": \"once\""
+ " },"
+ " {"
+ " \"state\": \"ready\","
+ " \"pause\": \"always\""
+ " },"
+ " {"
+ " \"state\": \"partner-down\","
+ " \"pause\": \"never\""
+ " }"
+ " ]"
+ " }"
+ " }"
+ "]";
+
+ HAImplPtr impl(new HAImpl());
+ ASSERT_NO_THROW(impl->configure(Element::fromJSON(ha_config)));
+ EXPECT_EQ("server1", impl->getConfig()->getThisServerName());
+ EXPECT_EQ(HAConfig::LOAD_BALANCING, impl->getConfig()->getHAMode());
+ EXPECT_FALSE(impl->getConfig()->amSendingLeaseUpdates());
+ EXPECT_FALSE(impl->getConfig()->amSyncingLeases());
+ EXPECT_EQ(20000, impl->getConfig()->getSyncTimeout());
+ EXPECT_EQ(3, impl->getConfig()->getSyncPageLimit());
+ EXPECT_EQ(111, impl->getConfig()->getDelayedUpdatesLimit());
+ EXPECT_TRUE(impl->getConfig()->amAllowingCommRecovery());
+ EXPECT_EQ(8, impl->getConfig()->getHeartbeatDelay());
+ EXPECT_EQ(11, impl->getConfig()->getMaxResponseDelay());
+ EXPECT_EQ(5, impl->getConfig()->getMaxAckDelay());
+ EXPECT_EQ(20, impl->getConfig()->getMaxUnackedClients());
+ EXPECT_FALSE(impl->getConfig()->amWaitingBackupAck());
+
+ HAConfig::PeerConfigPtr cfg = impl->getConfig()->getThisServerConfig();
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server1", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8080/", cfg->getUrl().toText());
+ EXPECT_EQ(cfg->getLogLabel(), "server1 (http://127.0.0.1:8080/)");
+ EXPECT_EQ(HAConfig::PeerConfig::PRIMARY, cfg->getRole());
+ EXPECT_FALSE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
+
+ cfg = impl->getConfig()->getPeerConfig("server2");
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server2", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8081/", cfg->getUrl().toText());
+ EXPECT_EQ(cfg->getLogLabel(), "server2 (http://127.0.0.1:8081/)");
+ EXPECT_EQ(HAConfig::PeerConfig::SECONDARY, cfg->getRole());
+ EXPECT_TRUE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
+
+ cfg = impl->getConfig()->getPeerConfig("server3");
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server3", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8082/", cfg->getUrl().toText());
+ EXPECT_EQ(cfg->getLogLabel(), "server3 (http://127.0.0.1:8082/)");
+ EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
+ EXPECT_FALSE(cfg->isAutoFailover());
+ ASSERT_TRUE(cfg->getBasicAuth());
+ EXPECT_EQ("foo:bar", cfg->getBasicAuth()->getSecret());
+
+ // Verify that per-state configuration is correct.
+
+ HAConfig::StateConfigPtr state_cfg;
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_BACKUP_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_COMMUNICATION_RECOVERY_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_LOAD_BALANCING_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_PARTNER_DOWN_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_READY_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_ALWAYS, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_SYNCING_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_TERMINATED_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_WAITING_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_ONCE, state_cfg->getPausing());
+
+ // Verify multi-threading default values.
+ EXPECT_FALSE(impl->getConfig()->getEnableMultiThreading());
+ EXPECT_FALSE(impl->getConfig()->getHttpDedicatedListener());
+ EXPECT_EQ(0, impl->getConfig()->getHttpListenerThreads());
+ EXPECT_EQ(0, impl->getConfig()->getHttpClientThreads());
+}
+
+// Verifies that hot standby configuration is parsed correctly.
+TEST_F(HAConfigTest, configureHotStandby) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"basic-auth-user\": \"admin\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " },"
+ " {"
+ " \"name\": \"server3\","
+ " \"url\": \"http://127.0.0.1:8082/\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": false"
+ " }"
+ " ]"
+ " }"
+ "]";
+
+ HAImplPtr impl(new HAImpl());
+ ASSERT_NO_THROW(impl->configure(Element::fromJSON(ha_config)));
+ EXPECT_EQ("server1", impl->getConfig()->getThisServerName());
+ EXPECT_EQ(HAConfig::HOT_STANDBY, impl->getConfig()->getHAMode());
+ EXPECT_TRUE(impl->getConfig()->amSendingLeaseUpdates());
+ EXPECT_TRUE(impl->getConfig()->amSyncingLeases());
+ EXPECT_EQ(60000, impl->getConfig()->getSyncTimeout());
+ EXPECT_EQ(10000, impl->getConfig()->getSyncPageLimit());
+ EXPECT_EQ(0, impl->getConfig()->getDelayedUpdatesLimit());
+ EXPECT_FALSE(impl->getConfig()->amAllowingCommRecovery());
+ EXPECT_EQ(10000, impl->getConfig()->getHeartbeatDelay());
+ EXPECT_EQ(10000, impl->getConfig()->getMaxAckDelay());
+ EXPECT_EQ(10, impl->getConfig()->getMaxUnackedClients());
+ EXPECT_FALSE(impl->getConfig()->amWaitingBackupAck());
+
+ HAConfig::PeerConfigPtr cfg = impl->getConfig()->getThisServerConfig();
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server1", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8080/", cfg->getUrl().toText());
+ EXPECT_EQ(HAConfig::PeerConfig::PRIMARY, cfg->getRole());
+ EXPECT_FALSE(cfg->isAutoFailover());
+ ASSERT_TRUE(cfg->getBasicAuth());
+ EXPECT_EQ("admin:", cfg->getBasicAuth()->getSecret());
+
+ cfg = impl->getConfig()->getPeerConfig("server2");
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server2", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8081/", cfg->getUrl().toText());
+ EXPECT_EQ(HAConfig::PeerConfig::STANDBY, cfg->getRole());
+ EXPECT_TRUE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
+
+ cfg = impl->getConfig()->getPeerConfig("server3");
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server3", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8082/", cfg->getUrl().toText());
+ EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
+ EXPECT_FALSE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
+
+ HAConfig::StateConfigPtr state_cfg;
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_BACKUP_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_HOT_STANDBY_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_PARTNER_DOWN_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_READY_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_SYNCING_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_TERMINATED_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
+ getStateConfig(HA_WAITING_ST));
+ ASSERT_TRUE(state_cfg);
+ EXPECT_EQ(STATE_PAUSE_NEVER, state_cfg->getPausing());
+
+ // Verify multi-threading default values.
+ EXPECT_FALSE(impl->getConfig()->getEnableMultiThreading());
+ EXPECT_FALSE(impl->getConfig()->getHttpDedicatedListener());
+ EXPECT_EQ(0, impl->getConfig()->getHttpListenerThreads());
+ EXPECT_EQ(0, impl->getConfig()->getHttpClientThreads());
+}
+
+// Verifies that passive-backup configuration is parsed correctly.
+TEST_F(HAConfigTest, configurePassiveBackup) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"passive-backup\","
+ " \"wait-backup-ack\": true,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\""
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"backup\""
+ " },"
+ " {"
+ " \"name\": \"server3\","
+ " \"url\": \"http://127.0.0.1:8082/\","
+ " \"basic-auth-user\": \"keatest\","
+ " \"basic-auth-password-file\": \""
+ TEST_HTTP_DIR "/hiddenp\","
+ " \"role\": \"backup\""
+ " }"
+ " ]"
+ " }"
+ "]";
+
+ HAImplPtr impl(new HAImpl());
+ ASSERT_NO_THROW(impl->configure(Element::fromJSON(ha_config)));
+ EXPECT_EQ("server1", impl->getConfig()->getThisServerName());
+ EXPECT_EQ(HAConfig::PASSIVE_BACKUP, impl->getConfig()->getHAMode());
+ EXPECT_TRUE(impl->getConfig()->amSendingLeaseUpdates());
+ EXPECT_TRUE(impl->getConfig()->amWaitingBackupAck());
+
+ HAConfig::PeerConfigPtr cfg = impl->getConfig()->getThisServerConfig();
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server1", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8080/", cfg->getUrl().toText());
+ EXPECT_EQ(HAConfig::PeerConfig::PRIMARY, cfg->getRole());
+ EXPECT_FALSE(cfg->getBasicAuth());
+
+ cfg = impl->getConfig()->getPeerConfig("server2");
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server2", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8081/", cfg->getUrl().toText());
+ EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
+ EXPECT_FALSE(cfg->getBasicAuth());
+
+ cfg = impl->getConfig()->getPeerConfig("server3");
+ ASSERT_TRUE(cfg);
+ EXPECT_EQ("server3", cfg->getName());
+ EXPECT_EQ("http://127.0.0.1:8082/", cfg->getUrl().toText());
+ EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
+ ASSERT_TRUE(cfg->getBasicAuth());
+ EXPECT_EQ("a2VhdGVzdDpLZWFUZXN0", cfg->getBasicAuth()->getCredential());
+
+ // Verify multi-threading default values.
+ EXPECT_FALSE(impl->getConfig()->getEnableMultiThreading());
+ EXPECT_FALSE(impl->getConfig()->getHttpDedicatedListener());
+ EXPECT_EQ(0, impl->getConfig()->getHttpListenerThreads());
+ EXPECT_EQ(0, impl->getConfig()->getHttpClientThreads());
+}
+
+// This server name must not be empty.
+TEST_F(HAConfigTest, emptyServerName) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'this-server-name' value must not be empty");
+}
+
+// There must be a configuration provided for this server.
+TEST_F(HAConfigTest, nonMatchingServerName) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"foo\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "no peer configuration specified for the 'foo'");
+}
+
+// Error should be returned when mode is invalid.
+TEST_F(HAConfigTest, unsupportedMode) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"unsupported-mode\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "unsupported value 'unsupported-mode' for mode parameter");
+}
+
+// Error should be returned when heartbeat-delay is negative.
+TEST_F(HAConfigTest, negativeHeartbeatDelay) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"heartbeat-delay\": -1,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'heartbeat-delay' must not be negative");
+}
+
+// Error should be returned when heartbeat-delay is too large.
+TEST_F(HAConfigTest, largeHeartbeatDelay) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"heartbeat-delay\": 65536,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'heartbeat-delay' must not be greater than 65535");
+}
+
+// There must be at least two servers provided.
+TEST_F(HAConfigTest, singlePeer) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "secondary server required in the load balancing configuration");
+}
+
+// Server name must not be empty.
+TEST_F(HAConfigTest, emptyPeerName) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "peer name must not be empty");
+}
+
+// Can't have two servers with the same name.
+TEST_F(HAConfigTest, duplicatePeerName) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "peer with name 'server1' already specified");
+}
+
+// URL must be valid.
+TEST_F(HAConfigTest, invalidURL) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "invalid URL: url ://127.0.0.1:8080/ lacks http or"
+ " https scheme for server server2");
+}
+
+// URL hostname must be an address.
+TEST_F(HAConfigTest, badURLName) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://localhost:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "bad url 'http://localhost:8080/': "
+ "Failed to convert string to address 'localhost': "
+ "Invalid argument for server server2");
+}
+
+// URL HTTPS scheme is not supported.
+TEST_F(HAConfigTest, badURLHttps) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"https://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "bad url 'https://127.0.0.1:8080/': "
+ "https scheme is not supported for server server2 "
+ "where TLS is disabled");
+}
+
+// Only certain roles are allowed.
+TEST_F(HAConfigTest, unsupportedRole) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"unsupported\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "unsupported value 'unsupported' for role parameter");
+}
+
+// There must be exactly one primary server.
+TEST_F(HAConfigTest, twoPrimary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "multiple primary servers specified");
+}
+
+// There must be exactly one secondary server.
+TEST_F(HAConfigTest, twoSecondary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "multiple secondary servers specified");
+}
+
+// Only one standby server is allowed.
+TEST_F(HAConfigTest, twoStandby) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "multiple standby servers specified");
+}
+
+// Primary server is required for load balancing.
+TEST_F(HAConfigTest, loadBalancingNoPrimary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "primary server required in the load balancing configuration");
+}
+
+// Secondary server is required for load balancing.
+TEST_F(HAConfigTest, loadBalancingNoSecondary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "secondary server required in the load balancing configuration");
+}
+
+// Primary server is required for hot standby mode.
+TEST_F(HAConfigTest, hotStandbyNoPrimary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "primary server required in the hot standby configuration");
+}
+
+// Standby server is required for hot standby mode.
+TEST_F(HAConfigTest, hotStandbyNoStandby) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "standby server required in the hot standby configuration");
+}
+
+// Standby server must not be specified in the load balancing mode.
+TEST_F(HAConfigTest, loadBalancingStandby) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "standby servers not allowed in the load balancing configuration");
+}
+
+// Secondary server must not be specified in the hot standby mode.
+TEST_F(HAConfigTest, hotStandbySecondary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "secondary servers not allowed in the hot standby configuration");
+}
+
+// state-machine parameter must be a map.
+TEST_F(HAConfigTest, invalidStateMachine) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ],"
+ " \"state-machine\": ["
+ " {"
+ " \"state\": \"foo\","
+ " \"pause\": \"always\""
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'state-machine' parameter must be a map");
+}
+
+// states within state-machine must be a list.
+TEST_F(HAConfigTest, invalidStatesList) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ],"
+ " \"state-machine\": {"
+ " \"states\": {"
+ " }"
+ " }"
+ " }"
+ "]",
+ "'states' parameter must be a list");
+}
+
+// State name must be recognized.
+TEST_F(HAConfigTest, invalidStateName) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ],"
+ " \"state-machine\": {"
+ " \"states\": ["
+ " {"
+ " \"state\": \"foo\","
+ " \"pause\": \"always\""
+ " }"
+ " ]"
+ " }"
+ " }"
+ "]",
+ "unknown state foo");
+}
+
+// Pause value must be recognized.
+TEST_F(HAConfigTest, invalidPauseValue) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ],"
+ " \"state-machine\": {"
+ " \"states\": ["
+ " {"
+ " \"state\": \"waiting\","
+ " \"pause\": \"foo\""
+ " }"
+ " ]"
+ " }"
+ " }"
+ "]",
+ "unsupported value foo of 'pause' parameter");
+}
+
+// Must not specify configuration for the same state twice.
+TEST_F(HAConfigTest, duplicatedStates) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ],"
+ " \"state-machine\": {"
+ " \"states\": ["
+ " {"
+ " \"state\": \"waiting\","
+ " \"pause\": \"always\""
+ " },"
+ " {"
+ " \"state\": \"ready\","
+ " \"pause\": \"always\""
+ " },"
+ " {"
+ " \"state\": \"waiting\","
+ " \"pause\": \"always\""
+ " }"
+ " ]"
+ " }"
+ " }"
+ "]",
+ "duplicated configuration for the 'waiting' state");
+}
+
+// Test that wait-backup-ack must not be enabled in the load-balancing
+// configuration.
+TEST_F(HAConfigTest, waitBackupAckLoadBalancing) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"wait-backup-ack\": true,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'wait-backup-ack' must be set to false in the load balancing configuration");
+}
+
+// Test that wait-backup-ack must not be enabled in the hot-standby
+// configuration.
+TEST_F(HAConfigTest, waitBackupAckHotStandby) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"wait-backup-ack\": true,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'wait-backup-ack' must be set to false in the hot standby configuration");
+}
+
+// Test that secondary server is not allowed in the passive-backup mode.
+TEST_F(HAConfigTest, passiveBackupSecondaryServer) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"passive-backup\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\""
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"secondary\""
+ " }"
+ " ]"
+ " }"
+ "]",
+ "secondary servers not allowed in the passive backup configuration");
+}
+
+// Test that standby server is not allowed in the passive-backup mode.
+TEST_F(HAConfigTest, passiveBackupStandbyServer) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"passive-backup\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\""
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"standby\""
+ " }"
+ " ]"
+ " }"
+ "]",
+ "standby servers not allowed in the passive backup configuration");
+}
+
+// Test that primary server is required in the passive-backup mode.
+TEST_F(HAConfigTest, passiveBackupNoPrimary) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"passive-backup\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\""
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"backup\""
+ " }"
+ " ]"
+ " }"
+ "]",
+ "primary server required in the passive backup configuration");
+}
+
+// Test that empty name id is forbidden for basic HTTP authentication.
+TEST_F(HAConfigTest, invalidUser) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"basic-auth-user\": \"foo:bar\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "user 'foo:bar' must not contain a ':' in peer 'server2'");
+}
+
+// Test that setting delayed-updates-limit is not allowed in hot-standby mode.
+TEST_F(HAConfigTest, hotStandbyDelayedUpdatesLimit) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"hot-standby\","
+ " \"delayed-updates-limit\": 1,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"standby\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'delayed-updates-limit' must be set to 0 in the hot standby configuration");
+}
+
+// Test that setting delayed-updates-limit is not allowed in passive-backup mode.
+TEST_F(HAConfigTest, passiveBackupDelayedUpdatesLimit) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"passive-backup\","
+ " \"delayed-updates-limit\": 1,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\""
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\""
+ " }"
+ " ]"
+ " }"
+ "]",
+ "'delayed-updates-limit' must be set to 0 in the passive backup configuration");
+}
+
+#if (defined(WITH_OPENSSL) || defined(WITH_BOTAN_BOOST))
+/// Test that TLS parameters are correctly inherited.
+TEST_F(HAConfigTest, tlsParameterInheritance) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"my-server\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"!CA!/kea-ca.crt\","
+ " \"cert-file\": \"!CA!/kea-client.crt\","
+ " \"key-file\": \"!CA!/kea-client.key\","
+ " \"require-client-certs\": false,"
+ " \"restrict-commands\": true,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"my-server\","
+ " \"url\": \"https://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"overwrite\","
+ " \"trust-anchor\": \"!CA!\","
+ " \"cert-file\": \"!CA!/kea-server.crt\","
+ " \"key-file\": \"!CA!/kea-server.key\","
+ " \"url\": \"https://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " },"
+ " {"
+ " \"name\": \"disable\","
+ " \"trust-anchor\": \"\","
+ " \"cert-file\": \"\","
+ " \"key-file\": \"\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"backup\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ HAImplPtr impl(new HAImpl());
+ ASSERT_NO_THROW(impl->configure(Element::fromJSON(patched)));
+
+ // Check the global parameters.
+ std::string expected;
+ EXPECT_FALSE(impl->getConfig()->getTrustAnchor().unspecified());
+ expected = TEST_CA_DIR;
+ expected += "/kea-ca.crt";
+ EXPECT_EQ(expected, impl->getConfig()->getTrustAnchor().get());
+ EXPECT_FALSE(impl->getConfig()->getCertFile().unspecified());
+ expected = TEST_CA_DIR;
+ expected += "/kea-client.crt";
+ EXPECT_EQ(expected, impl->getConfig()->getCertFile().get());
+ EXPECT_FALSE(impl->getConfig()->getKeyFile().unspecified());
+ expected = TEST_CA_DIR;
+ expected += "/kea-client.key";
+ EXPECT_EQ(expected, impl->getConfig()->getKeyFile().get());
+ EXPECT_FALSE(impl->getConfig()->getRequireClientCerts());
+ EXPECT_TRUE(impl->getConfig()->getRestrictCommands());
+
+ // Check the first peer parameters: it inherits them from the global level.
+ HAConfig::PeerConfigPtr cfg = impl->getConfig()->getThisServerConfig();
+ ASSERT_TRUE(cfg);
+ EXPECT_TRUE(cfg->getTlsContext());
+
+ // Check the second peer parameters: it overwrites them.
+ cfg = impl->getConfig()->getPeerConfig("overwrite");
+ ASSERT_TRUE(cfg);
+ EXPECT_FALSE(cfg->getTrustAnchor().unspecified());
+ expected = TEST_CA_DIR;
+ EXPECT_EQ(expected, cfg->getTrustAnchor().get());
+ EXPECT_FALSE(cfg->getCertFile().unspecified());
+ expected = TEST_CA_DIR;
+ expected += "/kea-server.crt";
+ EXPECT_EQ(expected, cfg->getCertFile().get());
+ EXPECT_FALSE(cfg->getKeyFile().unspecified());
+ expected = TEST_CA_DIR;
+ expected += "/kea-server.key";
+ EXPECT_EQ(expected, cfg->getKeyFile().get());
+ EXPECT_TRUE(cfg->getTlsContext());
+
+ // Check the last peer parameters: it disables TLS by setting them to "".
+ cfg = impl->getConfig()->getPeerConfig("disable");
+ ASSERT_TRUE(cfg);
+ EXPECT_FALSE(cfg->getTrustAnchor().unspecified());
+ EXPECT_EQ("", cfg->getTrustAnchor().get());
+ EXPECT_FALSE(cfg->getCertFile().unspecified());
+ EXPECT_EQ("", cfg->getCertFile().get());
+ EXPECT_FALSE(cfg->getKeyFile().unspecified());
+ EXPECT_EQ("", cfg->getKeyFile().get());
+ // The TLS context should be null.
+ EXPECT_FALSE(cfg->getTlsContext());
+}
+
+// Test that a missing trust-anchor in the HTTPS parameter set raise an error.
+TEST_F(HAConfigTest, missingTrustAnchor) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"!CA!/kea-ca.crt\","
+ " \"cert-file\": \"!CA!/kea-client.crt\","
+ " \"key-file\": \"!CA!/kea-client.key\","
+ " \"require-client-certs\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"trust-anchor\": \"\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ std::string expected = "bad TLS config for server server2: ";
+ expected += "trust-anchor parameter is missing or empty: ";
+ expected += "all or none of TLS parameters must be set";
+ testInvalidConfig(patched, expected);
+}
+
+// Test that a missing cert-file in the HTTPS parameter set raise an error.
+TEST_F(HAConfigTest, missingCertFile) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"!CA!/kea-ca.crt\","
+ " \"cert-file\": \"!CA!/kea-client.crt\","
+ " \"key-file\": \"!CA!/kea-client.key\","
+ " \"require-client-certs\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"cert-file\": \"\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ std::string expected = "bad TLS config for server server2: ";
+ expected += "cert-file parameter is missing or empty: ";
+ expected += "all or none of TLS parameters must be set";
+ testInvalidConfig(patched, expected);
+}
+
+// Test that a missing key-file in the HTTPS parameter set raise an error.
+TEST_F(HAConfigTest, missingKeyFile) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"!CA!/kea-ca.crt\","
+ " \"cert-file\": \"!CA!/kea-client.crt\","
+ " \"key-file\": \"!CA!/kea-client.key\","
+ " \"require-client-certs\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"key-file\": \"\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ std::string expected = "bad TLS config for server server2: ";
+ expected += "key-file parameter is missing or empty: ";
+ expected += "all or none of TLS parameters must be set";
+ testInvalidConfig(patched, expected);
+}
+
+// Test that a bad trust-anchor in the HTTPS parameter set raise an error.
+TEST_F(HAConfigTest, badTrustAnchor) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"/this-file-does-not-exist\","
+ " \"cert-file\": \"!CA!/kea-client.crt\","
+ " \"key-file\": \"!CA!/kea-client.key\","
+ " \"require-client-certs\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ std::string expected = "bad TLS config for server server1: ";
+ expected += "load of CA file '/this-file-does-not-exist' failed: ";
+ // Backend dependent.
+#ifdef WITH_OPENSSL
+ expected += "No such file or directory";
+#else
+ expected += "I/O error: DataSource: Failure opening file /this-file-does-not-exist";
+#endif
+ testInvalidConfig(patched, expected);
+}
+
+// Test that a bad cert-file in the HTTPS parameter set raise an error.
+TEST_F(HAConfigTest, badCertFile) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"!CA!/kea-ca.crt\","
+ " \"cert-file\": \"/this-file-does-not-exist\","
+ " \"key-file\": \"!CA!/kea-client.key\","
+ " \"require-client-certs\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ std::string expected = "bad TLS config for server server1: ";
+ expected += "load of cert file '/this-file-does-not-exist' failed: ";
+ // Backend dependent.
+#ifdef WITH_OPENSSL
+ expected += "No such file or directory";
+#else
+ expected += "I/O error: DataSource: Failure opening file /this-file-does-not-exist";
+#endif
+ testInvalidConfig(patched, expected);
+}
+
+// Test that a bad key-file in the HTTPS parameter set raise an error.
+TEST_F(HAConfigTest, badKeyFile) {
+ const std::string ha_config =
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"trust-anchor\": \"!CA!/kea-ca.crt\","
+ " \"cert-file\": \"!CA!/kea-client.crt\","
+ " \"key-file\": \"/this-file-does-not-exist\","
+ " \"require-client-certs\": false,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]";
+ const std::string& patched = replaceInConfig(ha_config, "!CA!",
+ TEST_CA_DIR);
+ std::string expected = "bad TLS config for server server1: ";
+ expected += "load of private key file '/this-file-does-not-exist' failed: ";
+ // Backend dependent.
+#ifdef WITH_OPENSSL
+ expected += "No such file or directory";
+#else
+ expected += "I/O error: DataSource: Failure opening file /this-file-does-not-exist";
+#endif
+ testInvalidConfig(patched, expected);
+}
+#endif // WITH_OPENSSL || WITH_BOTAN_BOOST
+
+// Test that conversion of the role names works correctly.
+TEST_F(HAConfigTest, stringToRole) {
+ EXPECT_EQ(HAConfig::PeerConfig::PRIMARY,
+ HAConfig::PeerConfig::stringToRole("primary"));
+ EXPECT_EQ(HAConfig::PeerConfig::SECONDARY,
+ HAConfig::PeerConfig::stringToRole("secondary"));
+ EXPECT_EQ(HAConfig::PeerConfig::STANDBY,
+ HAConfig::PeerConfig::stringToRole("standby"));
+ EXPECT_EQ(HAConfig::PeerConfig::BACKUP,
+ HAConfig::PeerConfig::stringToRole("backup"));
+ EXPECT_THROW(HAConfig::PeerConfig::stringToRole("unsupported"),
+ BadValue);
+}
+
+// Test that role name is generated correctly.
+TEST_F(HAConfigTest, roleToString) {
+ EXPECT_EQ("primary",
+ HAConfig::PeerConfig::roleToString(HAConfig::PeerConfig::PRIMARY));
+ EXPECT_EQ("secondary",
+ HAConfig::PeerConfig::roleToString(HAConfig::PeerConfig::SECONDARY));
+ EXPECT_EQ("standby",
+ HAConfig::PeerConfig::roleToString(HAConfig::PeerConfig::STANDBY));
+ EXPECT_EQ("backup",
+ HAConfig::PeerConfig::roleToString(HAConfig::PeerConfig::BACKUP));
+}
+
+// Test that conversion of the HA mode names works correctly.
+TEST_F(HAConfigTest, stringToHAMode) {
+ EXPECT_EQ(HAConfig::LOAD_BALANCING, HAConfig::stringToHAMode("load-balancing"));
+ EXPECT_EQ(HAConfig::HOT_STANDBY, HAConfig::stringToHAMode("hot-standby"));
+ EXPECT_EQ(HAConfig::PASSIVE_BACKUP, HAConfig::stringToHAMode("passive-backup"));
+}
+
+// Test that HA mode name is generated correctly.
+TEST_F(HAConfigTest, HAModeToString) {
+ EXPECT_EQ("load-balancing", HAConfig::HAModeToString(HAConfig::LOAD_BALANCING));
+ EXPECT_EQ("hot-standby", HAConfig::HAModeToString(HAConfig::HOT_STANDBY));
+ EXPECT_EQ("passive-backup", HAConfig::HAModeToString(HAConfig::PASSIVE_BACKUP));
+}
+
+// Test that conversion of the 'pause' value works correctly.
+TEST_F(HAConfigTest, stringToPausing) {
+ EXPECT_EQ(STATE_PAUSE_ALWAYS,
+ HAConfig::StateConfig::stringToPausing("always"));
+ EXPECT_EQ(STATE_PAUSE_NEVER,
+ HAConfig::StateConfig::stringToPausing("never"));
+ EXPECT_EQ(STATE_PAUSE_ONCE,
+ HAConfig::StateConfig::stringToPausing("once"));
+}
+
+// Test that pause parameter value is generated correctly.
+TEST_F(HAConfigTest, pausingToString) {
+ EXPECT_EQ("always",
+ HAConfig::StateConfig::pausingToString(STATE_PAUSE_ALWAYS));
+ EXPECT_EQ("never",
+ HAConfig::StateConfig::pausingToString(STATE_PAUSE_NEVER));
+ EXPECT_EQ("once",
+ HAConfig::StateConfig::pausingToString(STATE_PAUSE_ONCE));
+}
+
+// Verifies permutations of HA+MT configuration.
+TEST_F(HAConfigTest, multiThreadingPermutations) {
+
+ // Structure describing a test scenario.
+ struct Scenario {
+ std::string desc_; // Description of the scenario.
+ std::string mt_json_; // multi-threading config to use.
+ bool dhcp_mt_enabled_; // True if DHCP multi-threading is enabled.
+ uint32_t dhcp_threads_; // Value of DHCP thread-pool-size.
+ bool exp_ha_mt_enabled_; // If HA+MT should be enabled
+ bool exp_listener_; // If HA+MT should use dedicated listener.
+ uint32_t exp_listener_threads_; // Expected number of listener threads.
+ uint32_t exp_client_threads_; // Expected number of client threads.
+ };
+
+ // Mnemonic constants.
+ bool dhcp_mt = true;
+ bool ha_mt = true;
+ bool listener = true;
+
+ // Number of threads the system reports as supported.
+ uint32_t sys_threads = MultiThreadingMgr::detectThreadCount();
+
+ std::vector<Scenario> scenarios {
+ {
+ "1 no ha+mt/default",
+ "",
+ dhcp_mt, 4,
+ !ha_mt, !listener, 0, 0
+ },
+ {
+ "2 dhcp mt enabled, ha mt disabled",
+ makeHAMtJson(!ha_mt, !listener, 0, 0),
+ dhcp_mt, 4,
+ !ha_mt, !listener, 0, 0
+ },
+ {
+ "3 dhcp mt disabled, ha mt enabled",
+ makeHAMtJson(ha_mt, listener, 0, 0),
+ !dhcp_mt, 4,
+ !ha_mt, !listener, 0, 0
+ },
+ {
+ "4 dhcp mt enabled, ha mt enabled, listener disabled",
+ makeHAMtJson(ha_mt, !listener, 0, 0),
+ dhcp_mt, 4,
+ ha_mt, !listener, 4, 4
+ },
+ {
+ "5 dhcp mt enabled, ha mt enabled, listener enabled",
+ makeHAMtJson(ha_mt, listener, 0, 0),
+ dhcp_mt, 4,
+ ha_mt, listener, 4, 4
+ },
+ {
+ "6 explicit DHCP threads, explicit thread values",
+ makeHAMtJson(ha_mt, listener, 5, 6),
+ dhcp_mt, 4,
+ ha_mt, listener, 5, 6
+ },
+ {
+ "7 explicit DHCP threads, zero thread values",
+ makeHAMtJson(ha_mt, listener, 0, 0),
+ dhcp_mt, 8,
+ ha_mt, listener, 8, 8
+ },
+ {
+ "8 DHCP auto detect threads, zero thread values",
+ // Special case: if system reports supported threads as 0
+ // then HA+MT should be disabled. Otherwise it should
+ // be enabled with listener and client threads set to the
+ // reported value.
+ makeHAMtJson(ha_mt, listener, 0, 0),
+ dhcp_mt, 0,
+ (sys_threads > 0), listener, sys_threads, sys_threads
+ }
+ };
+
+ // Iterate over the scenarios.
+ for (auto const& scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_);
+
+ // Build the HA JSON configuration.
+ std::stringstream ss;
+ ss <<
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"passive-backup\","
+ " \"wait-backup-ack\": true,"
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\""
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \"http://127.0.0.1:8081/\","
+ " \"role\": \"backup\""
+ " }"
+ " ]";
+
+ if (!scenario.mt_json_.empty()) {
+ ss << "," << scenario.mt_json_;
+ }
+
+ ss << "}]";
+ ConstElementPtr config_json;
+ ASSERT_NO_THROW_LOG(config_json = Element::fromJSON(ss.str()));
+
+ // Set DHCP multi-threading configuration in CfgMgr.
+ setDHCPMultiThreadingConfig(scenario.dhcp_mt_enabled_, scenario.dhcp_threads_);
+
+ // Create and configure the implementation.
+ HAImplPtr impl(new HAImpl());
+ ASSERT_NO_THROW_LOG(impl->configure(config_json));
+
+ // Fetch the updated config.
+ HAConfigPtr ha_config = impl->getConfig();
+
+ // Verify the configuration is as expected.
+ if (!scenario.exp_ha_mt_enabled_) {
+ // When HA+MT is disabled, the other values are moot.
+ ASSERT_FALSE(ha_config->getEnableMultiThreading());
+ } else {
+ ASSERT_TRUE(ha_config->getEnableMultiThreading());
+ EXPECT_EQ(ha_config->getHttpDedicatedListener(), scenario.exp_listener_);
+ EXPECT_EQ(ha_config->getHttpListenerThreads(), scenario.exp_listener_threads_);
+ EXPECT_EQ(ha_config->getHttpClientThreads(), scenario.exp_client_threads_);
+ }
+ }
+}
+
+// Check that an IPv6 address can be used as part of a value for "url".
+TEST_F(HAConfigTest, ipv6Url) {
+ std::string const ha_config(R"(
+ [
+ {
+ "mode": "load-balancing",
+ "peers": [
+ {
+ "name": "server1",
+ "role": "primary",
+ "url": "http://[2001:db8::1]:8080/"
+ },
+ {
+ "name": "server2",
+ "role": "secondary",
+ "url": "http://[2001:db8::2]:8080/"
+ }
+ ],
+ "this-server-name": "server1"
+ }
+ ]
+ )");
+
+ // Configure HA.
+ HAImplPtr impl(new HAImpl());
+ ASSERT_NO_THROW_LOG(impl->configure(Element::fromJSON(ha_config)));
+
+ // Check the URL.
+ EXPECT_EQ(impl->getConfig()->getThisServerConfig()->getUrl().toText(), "http://[2001:db8::1]:8080/");
+}
+
+} // end of anonymous namespace