summaryrefslogtreecommitdiffstats
path: root/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc')
-rw-r--r--src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc738
1 files changed, 738 insertions, 0 deletions
diff --git a/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..1ec7b87
--- /dev/null
+++ b/src/bin/netconf/tests/netconf_cfg_mgr_unittests.cc
@@ -0,0 +1,738 @@
+// Copyright (C) 2018-2021 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 <netconf/netconf_cfg_mgr.h>
+#include <netconf/parser_context.h>
+#include <exceptions/exceptions.h>
+#include <cc/command_interpreter.h>
+#include <process/testutils/d_test_stubs.h>
+#include <process/d_cfg_mgr.h>
+#include <yang/yang_models.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <netconf/tests/test_libraries.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::netconf;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::http;
+using namespace isc::process;
+using namespace isc::yang;
+
+namespace {
+
+/// @brief Almost regular netconf CfgMgr with internal parse method exposed.
+class NakedNetconfCfgMgr : public NetconfCfgMgr {
+public:
+ using NetconfCfgMgr::parse;
+};
+
+// Tests construction of NetconfCfgMgr class.
+TEST(NetconfCfgMgr, construction) {
+ boost::scoped_ptr<NetconfCfgMgr> cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW_LOG(cfg_mgr.reset(new NetconfCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ NetconfConfigPtr context;
+ ASSERT_NO_THROW_LOG(context = cfg_mgr->getNetconfConfig());
+ EXPECT_TRUE(context);
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+}
+
+// Tests if getContext can be retrieved.
+TEST(NetconfCfgMgr, getContext) {
+ NetconfCfgMgr cfg_mgr;
+
+ NetconfConfigPtr ctx;
+ ASSERT_NO_THROW_LOG(ctx = cfg_mgr.getNetconfConfig());
+ ASSERT_TRUE(ctx);
+}
+
+// Tests if context can store and retrieve managed server information.
+TEST(NetconfCfgMgr, contextServer) {
+
+ NetconfConfig ctx;
+
+ // Check managed server parameters.
+ // By default, there are no server stored.
+ ASSERT_TRUE(ctx.getCfgServersMap());
+ EXPECT_EQ(0, ctx.getCfgServersMap()->size());
+
+ CfgControlSocketPtr
+ socket1(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket1",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server1(new CfgServer("model1", socket1));
+ CfgControlSocketPtr
+ socket2(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket2",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server2(new CfgServer("model2", socket2));
+ CfgControlSocketPtr
+ socket3(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket3",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server3(new CfgServer("model3", socket3));
+ CfgControlSocketPtr
+ socket4(new CfgControlSocket(CfgControlSocket::Type::UNIX,
+ "socket4",
+ Url("http://127.0.0.1:8000/")));
+ CfgServerPtr server4(new CfgServer("model4", socket4));
+
+ // Ok, now set the server for D2
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("d2", server1)));
+
+ // Now check the values returned
+ EXPECT_EQ(1, ctx.getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("d2"));
+ EXPECT_EQ(server1, ctx.getCfgServersMap()->at("d2"));
+ EXPECT_THROW(ctx.getCfgServersMap()->at("dhcp4"), std::out_of_range);
+
+ // Now set the v6 server and sanity check again
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("dhcp6", server2)));
+
+ // Should be possible to retrieve two servers
+ EXPECT_EQ(2, ctx.getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("dhcp6"));
+ EXPECT_EQ(server1, ctx.getCfgServersMap()->at("d2"));
+ EXPECT_EQ(server2, ctx.getCfgServersMap()->at("dhcp6"));
+
+ // Finally, set all servers.
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("dhcp4", server3)));
+ EXPECT_NO_THROW(ctx.getCfgServersMap()->insert(make_pair("ca", server4)));
+ EXPECT_EQ(4, ctx.getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("dhcp4"));
+ ASSERT_NO_THROW_LOG(ctx.getCfgServersMap()->at("ca"));
+ EXPECT_EQ(server3, ctx.getCfgServersMap()->at("dhcp4"));
+ EXPECT_EQ(server4, ctx.getCfgServersMap()->at("ca"));
+}
+
+// Tests if the context can store and retrieve hook libs information.
+TEST(NetconfCfgMgr, contextHookParams) {
+ NetconfConfig ctx;
+
+ // By default there should be no hooks.
+ HooksConfig& libs = ctx.getHooksConfig();
+ EXPECT_TRUE(libs.get().empty());
+
+ libs.add("libone.so", ConstElementPtr());
+ libs.add("libtwo.so", Element::fromJSON("{\"foo\": true}"));
+ libs.add("libthree.so", Element::fromJSON("{\"bar\": 42}"));
+
+ const HooksConfig& stored_libs = ctx.getHooksConfig();
+ EXPECT_EQ(3, stored_libs.get().size());
+
+ EXPECT_EQ(libs.get(), stored_libs.get());
+}
+
+// Tests if the context can store and retrieve globals.
+TEST(NetconfCfgMgr, contextGlobals) {
+ NetconfConfig ctx;
+
+ // By default there should be no globals.
+ ConstElementPtr globals = ctx.getConfiguredGlobals();
+ ASSERT_TRUE(globals);
+ ASSERT_EQ(Element::map, globals->getType());
+ EXPECT_EQ(0, globals->mapValue().size());
+
+ // Attempting to extract globals from a non-map should throw.
+ ASSERT_THROW(ctx.extractConfiguredGlobals(Element::create(777)), BadValue);
+
+ // Now let's create a configuration from which to extract global scalars.
+ // Extraction (currently) has no business logic, so the elements we use
+ // can be arbitrary.
+ ConstElementPtr global_cfg;
+ string global_cfg_str =
+ "{\n"
+ " \"astring\": \"okay\",\n"
+ " \"amap\": { \"not-this\":777, \"not-that\": \"poo\" },\n"
+ " \"anint\": 444,\n"
+ " \"alist\": [ 1, 2, 3 ],\n"
+ " \"abool\": true\n"
+ "}\n";
+ ASSERT_NO_THROW_LOG(global_cfg = Element::fromJSON(global_cfg_str));
+
+ // Extract globals from the config.
+ ASSERT_NO_THROW_LOG(ctx.extractConfiguredGlobals(global_cfg));
+
+ // Now see if the extract was correct.
+ globals = ctx.getConfiguredGlobals();
+ ASSERT_TRUE(globals);
+ ASSERT_EQ(Element::map, globals->getType());
+ EXPECT_NE(0, globals->mapValue().size());
+
+ // Maps and lists should be excluded.
+ for (auto it : globals->mapValue()) {
+ if (it.first == "astring") {
+ ASSERT_EQ(Element::string, it.second->getType());
+ EXPECT_EQ("okay", it.second->stringValue());
+ } else if (it.first == "anint") {
+ ASSERT_EQ(Element::integer, it.second->getType());
+ EXPECT_EQ(444, it.second->intValue());
+ } else if (it.first == "abool") {
+ ASSERT_EQ(Element::boolean, it.second->getType());
+ EXPECT_TRUE(it.second->boolValue());
+ } else {
+ ADD_FAILURE() << "unexpected element found:" << it.first;
+ }
+ }
+}
+
+/// @brief Netconf configurations used in tests.
+const char* NETCONF_CONFIGS[] = {
+
+ // configuration 0: empty (nothing specified)
+ "{ }",
+
+ // Configuration 1: global parameters only (no server, not hooks)
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": false,\n"
+ " \"validate-changes\": false\n"
+ "}",
+
+ // Configuration 2: 1 server
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"boot-update\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 3: all 4 servers
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"boot-update\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " },\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v6\"\n"
+ " }\n"
+ " },\n"
+ " \"d2\": {\n"
+ " \"subscribe-changes\": false,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-d2\"\n"
+ " }\n"
+ " },\n"
+ " \"ca\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-ca\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 4: 1 server and hooks
+ // Netconf is able to load hook libraries that augment its operation.
+ // The primary functionality is the ability to add new commands.
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " \"hooks-libraries\": ["
+ " {"
+ " \"library\": \"%LIBRARY%\","
+ " \"parameters\": {\n"
+ " \"param1\": \"foo\"\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ "}",
+
+ // Configuration 5: 1 server (d2 only)
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"d2\": {\n"
+ " \"subscribe-changes\": false,\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-d2\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 6: 1 server (dhcp6 only)
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v6\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 7: 2 servers with user contexts and comments
+ "{\n"
+ " \"user-context\": { \"comment\": \"Indirect comment\" },\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"comment\": \"dhcp4 server\",\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v4\"\n"
+ " }\n"
+ " },\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-name\": \"/tmp/socket-v6\",\n"
+ " \"user-context\": { \"version\": 1 }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 8: empty server with no control socket
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"comment\": \"empty map not allowed\"\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 9: empty control socket
+ "{\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": false,\n"
+ " \"validate-changes\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"control-socket\": {\n"
+ " \"comment\": \"empty map not allowed\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 10: bad socket type
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"tcp\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}",
+
+ // Configuration 11: invalid socket Url
+ "{\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp6\": {\n"
+ " \"control-socket\": {\n"
+ " \"socket-url\": \"bad\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}"
+};
+
+// Tests the handling of bad socket type. Can't use the fixture class
+// because the Netconf parser does not allow bad socket types.
+TEST(NetconfParser, badSocketType) {
+ ConstElementPtr json;
+ ParserContext parser;
+ EXPECT_NO_THROW(json = parser.parseString(NETCONF_CONFIGS[10],
+ ParserContext::PARSER_JSON));
+ ConstElementPtr answer;
+ NakedNetconfCfgMgr cfg_mgr;
+ EXPECT_NO_THROW(answer = cfg_mgr.parse(json, false));
+ int rcode = 0;
+ string expected =
+ "\"Unknown control socket type: tcp 'tcp' (<string>:5:32)\"";
+ EXPECT_EQ(expected, parseAnswer(rcode, answer)->str());
+ EXPECT_EQ(1, rcode);
+}
+
+/// @brief Class used for testing CfgMgr
+class NetconfParserTest : public isc::process::ConfigParseTest {
+public:
+
+ /// @brief Tries to load input text as a configuration
+ ///
+ /// @param config text containing input configuration
+ /// @param expected_answer expected result of configuration (0 = success)
+ void configParse(const char* config, int expected_answer) {
+ isc::netconf::ParserContext parser;
+ ConstElementPtr json = parser.parseString(config, ParserContext::PARSER_SUB_NETCONF);
+
+ EXPECT_NO_THROW(answer_ = cfg_mgr_.parse(json, false));
+ EXPECT_TRUE(checkAnswer(expected_answer));
+ }
+
+ /// @brief Replaces %LIBRARY% with specified library name
+ ///
+ /// @param config input config text (should contain "%LIBRARY%" string)
+ /// @param lib_name %LIBRARY% will be replaced with that name
+ /// @return configuration text with library name replaced
+ string pathReplacer(const char* config, const char* lib_name) {
+ string txt(config);
+ txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name));
+ return (txt);
+ }
+
+ /// Configuration Manager (used in tests)
+ NakedNetconfCfgMgr cfg_mgr_;
+};
+
+// This test verifies if an empty config is handled properly. In practice such
+// a config makes little sense, but perhaps it's ok for a default deployment.
+TEST_F(NetconfParserTest, configParseEmpty) {
+ configParse(NETCONF_CONFIGS[0], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(0, ctx->getCfgServersMap()->size());
+}
+
+// This test verifies if a config with only globals is handled properly.
+TEST_F(NetconfParserTest, configParseGlobalOnly) {
+ configParse(NETCONF_CONFIGS[1], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(0, ctx->getCfgServersMap()->size());
+ ConstElementPtr globals = ctx->getConfiguredGlobals();
+ ASSERT_TRUE(globals);
+ string expected = "{ "
+ "\"boot-update\": false, "
+ "\"subscribe-changes\": false, "
+ "\"validate-changes\": false }";
+ EXPECT_EQ(expected, globals->str());
+}
+
+// Tests if an empty (i.e. without a control socket) can be configured.
+// Note that the syntax required the server map to not be really empty.
+TEST_F(NetconfParserTest, configParseEmptyCfgServer) {
+ configParse(NETCONF_CONFIGS[8], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ // Defaults.
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ EXPECT_FALSE(socket);
+}
+
+// This tests default values using a server with empty control socket
+// Note that the syntax required the control socket map to not be really empty.
+TEST_F(NetconfParserTest, configParseDefaults) {
+ configParse(NETCONF_CONFIGS[9], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ // Globals overwrite defaults.
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_FALSE(server->getSubscribeChanges());
+ EXPECT_FALSE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+
+ // Checking default.
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// Tests if a single DHCPv4 server can be configured.
+TEST_F(NetconfParserTest, configParseServerDhcp4) {
+ configParse(NETCONF_CONFIGS[2], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ // Locals overwrite globals.
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v4", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// Tests if a single D2 server can be configured.
+TEST_F(NetconfParserTest, configParseServerD2) {
+ configParse(NETCONF_CONFIGS[5], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("d2"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("d2");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP_DDNS, server->getModel());
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_FALSE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-d2", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// Tests if a single DHCPv6 server can be configured.
+TEST_F(NetconfParserTest, configParseServerDhcp6) {
+ configParse(NETCONF_CONFIGS[6], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(1, ctx->getCfgServersMap()->size());
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp6"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp6");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP6_SERVER, server->getModel());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v6", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+}
+
+// This tests if all 4 servers can be configured and makes sure the parser
+// doesn't confuse them.
+TEST_F(NetconfParserTest, configParse4Servers) {
+ configParse(NETCONF_CONFIGS[3], 0);
+
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(ctx);
+ ASSERT_TRUE(ctx->getCfgServersMap());
+ EXPECT_EQ(4, ctx->getCfgServersMap()->size());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP4_SERVER, server->getModel());
+ EXPECT_TRUE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v4", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("dhcp6"));
+ server = ctx->getCfgServersMap()->at("dhcp6");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP6_SERVER, server->getModel());
+ socket = server->getCfgControlSocket();
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-v6", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("d2"));
+ server = ctx->getCfgServersMap()->at("d2");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_DHCP_DDNS, server->getModel());
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_FALSE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-d2", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ ASSERT_NO_THROW_LOG(ctx->getCfgServersMap()->at("ca"));
+ server = ctx->getCfgServersMap()->at("ca");
+ ASSERT_TRUE(server);
+ EXPECT_EQ(KEA_CTRL_AGENT, server->getModel());
+ EXPECT_FALSE(server->getBootUpdate());
+ EXPECT_TRUE(server->getSubscribeChanges());
+ EXPECT_TRUE(server->getValidateChanges());
+ socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+ EXPECT_EQ(CfgControlSocket::Type::STDOUT, socket->getType());
+ EXPECT_EQ("/tmp/socket-ca", socket->getName());
+ EXPECT_EQ("http://127.0.0.1:8000/", socket->getUrl().toText());
+
+ // Check unparsing.
+ string expected = "{\n"
+ " \"Netconf\": {\n"
+ " \"boot-update\": false,\n"
+ " \"managed-servers\": {\n"
+ " \"dhcp4\": {\n"
+ " \"model\": \"kea-dhcp4-server\",\n"
+ " \"boot-update\": true,\n"
+ " \"subscribe-changes\": true,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-v4\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " },\n"
+ " \"dhcp6\": {\n"
+ " \"model\": \"kea-dhcp6-server\",\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": true,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-v6\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " },\n"
+ " \"d2\": {\n"
+ " \"model\": \"kea-dhcp-ddns\",\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": false,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-d2\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " },\n"
+ " \"ca\": {\n"
+ " \"model\": \"kea-ctrl-agent\",\n"
+ " \"boot-update\": false,\n"
+ " \"subscribe-changes\": true,\n"
+ " \"validate-changes\": true,\n"
+ " \"control-socket\": {\n"
+ " \"socket-type\": \"stdout\",\n"
+ " \"socket-name\": \"/tmp/socket-ca\",\n"
+ " \"socket-url\": \"http://127.0.0.1:8000/\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " \"hooks-libraries\": [ ]\n"
+ " }\n"
+ "}";
+ isc::test::runToElementTest<NetconfConfig>(expected, *ctx);
+}
+
+// Tests the handling of invalid socket URL.
+TEST_F(NetconfParserTest, configParseInvalidSocketUrl) {
+ configParse(NETCONF_CONFIGS[11], 1);
+ int rcode = 0;
+ string expected =
+ "\"invalid control socket url: url bad lacks http or https scheme "
+ "'bad' (<string>:5:31)\"";
+ EXPECT_EQ(expected, parseAnswer(rcode, answer_)->str());
+}
+
+// This test checks that the config file with hook library specified can be
+// loaded. This one is a bit tricky, because the parser sanity checks the lib
+// name. In particular, it checks if such a library exists. Therefore we
+// can't use NETCONF_CONFIGS[4] as is, but need to run it through path replacer.
+TEST_F(NetconfParserTest, configParseHooks) {
+ // Create the configuration with proper lib path.
+ string cfg = pathReplacer(NETCONF_CONFIGS[4], BASIC_CALLOUT_LIBRARY);
+ // The configuration should be successful.
+ configParse(cfg.c_str(), 0);
+
+ // The context now should have the library specified.
+ NetconfConfigPtr ctx = cfg_mgr_.getNetconfConfig();
+ const HookLibsCollection libs = ctx->getHooksConfig().get();
+ ASSERT_EQ(1, libs.size());
+ EXPECT_EQ(string(BASIC_CALLOUT_LIBRARY), libs[0].first);
+ ASSERT_TRUE(libs[0].second);
+ EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str());
+}
+
+// This test checks comments.
+TEST_F(NetconfParserTest, comments) {
+ configParse(NETCONF_CONFIGS[7], 0);
+ NetconfConfigPtr netconf_ctx = cfg_mgr_.getNetconfConfig();
+ ASSERT_TRUE(netconf_ctx);
+
+ // Check global user context.
+ ConstElementPtr ctx = netconf_ctx->getContext();
+ ASSERT_TRUE(ctx);
+ ASSERT_EQ(1, ctx->size());
+ ASSERT_TRUE(ctx->get("comment"));
+ EXPECT_EQ("\"Indirect comment\"", ctx->get("comment")->str());
+
+ // There is a DHCP4 server.
+ ASSERT_TRUE(netconf_ctx->getCfgServersMap());
+ ASSERT_NO_THROW_LOG(netconf_ctx->getCfgServersMap()->at("dhcp4"));
+ CfgServerPtr server = netconf_ctx->getCfgServersMap()->at("dhcp4");
+ ASSERT_TRUE(server);
+
+ // Check DHCP4 server user context.
+ ConstElementPtr ctx4 = server->getContext();
+ ASSERT_TRUE(ctx4);
+ ASSERT_EQ(1, ctx4->size());
+ ASSERT_TRUE(ctx4->get("comment"));
+ EXPECT_EQ("\"dhcp4 server\"", ctx4->get("comment")->str());
+
+ // There is a DHCP6 server.
+ ASSERT_NO_THROW_LOG(netconf_ctx->getCfgServersMap()->at("dhcp6"));
+ server = netconf_ctx->getCfgServersMap()->at("dhcp6");
+ ASSERT_TRUE(server);
+
+ // There is a DHCP6 control socket.
+ CfgControlSocketPtr socket = server->getCfgControlSocket();
+ ASSERT_TRUE(socket);
+
+ // Check DHCP6 control socket user context.
+ ConstElementPtr ctx6 = socket->getContext();
+ ASSERT_TRUE(ctx6);
+ ASSERT_EQ(1, ctx6->size());
+ ASSERT_TRUE(ctx6->get("version"));
+ EXPECT_EQ("1", ctx6->get("version")->str());
+}
+
+} // namespace