]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[65-libyang-config-translator] Added config translator code and tests
authorFrancis Dupont <fdupont@isc.org>
Wed, 24 Oct 2018 14:01:45 +0000 (16:01 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 24 Oct 2018 14:01:45 +0000 (16:01 +0200)
src/lib/yang/Makefile.am
src/lib/yang/tests/Makefile.am
src/lib/yang/tests/adaptor_config_unittests.cc
src/lib/yang/tests/config_unittests.cc [new file with mode: 0644]
src/lib/yang/tests/json_configs.h [new file with mode: 0644]
src/lib/yang/tests/translator_utils_unittests.cc
src/lib/yang/translator_config.cc [new file with mode: 0644]
src/lib/yang/translator_config.h [new file with mode: 0644]

index 27a8fe0adfbc72a2203d1a500b1cd9bb8998722e..7086577fa7198806efaeeff1e5c5a9f53150d62d 100644 (file)
@@ -28,6 +28,7 @@ libkea_yang_la_SOURCES += translator_host.cc translator_host.h
 libkea_yang_la_SOURCES += translator_subnet.cc translator_subnet.h
 libkea_yang_la_SOURCES += translator_shared_network.cc
 libkea_yang_la_SOURCES += translator_shared_network.h
+libkea_yang_la_SOURCES += translator_config.cc translator_config.h
 libkea_yang_la_SOURCES += yang_models.h
 
 libkea_yang_la_LIBADD =  $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
@@ -52,6 +53,7 @@ libkea_yang_include_HEADERS = \
        sysrepo_error.h \
        translator.h \
        translator_class.h \
+       translator_config.h \
        translator_control_socket.h \
        translator_database.h \
        translator_host.h \
index 4a8f091ece4147e4839560d496e126a53954989c..047478b2ce09146b2293e5db17bd654801645102 100644 (file)
@@ -17,7 +17,7 @@ EXTRA_DIST = keatest-module.yang
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
-run_unittests_SOURCES  = yang_configs.h
+run_unittests_SOURCES  = json_configs.h yang_configs.h
 run_unittests_SOURCES += adaptor_unittests.cc
 run_unittests_SOURCES += adaptor_option_unittests.cc
 run_unittests_SOURCES += adaptor_pool_unittests.cc
@@ -38,6 +38,7 @@ run_unittests_SOURCES += translator_host_unittests.cc
 run_unittests_SOURCES += translator_subnet_unittests.cc
 run_unittests_SOURCES += translator_shared_network_unittests.cc
 run_unittests_SOURCES += translator_utils_unittests.cc
+run_unittests_SOURCES += config_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
index 6f1eb9017e2373dc8c045e5b4a578c6a9da695ef..a4d873f3776688f233df82ccacc7493222da60a5 100644 (file)
@@ -37,7 +37,7 @@ void testFile(const std::string& fname, bool v6, ElementPtr& result) {
 
     string decommented = decommentJSONfile(fname);
 
-    cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
+    //cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
 
     EXPECT_NO_THROW(json = Element::fromJSONFile(decommented, true));
     reference_json = moveComments(json);
diff --git a/src/lib/yang/tests/config_unittests.cc b/src/lib/yang/tests/config_unittests.cc
new file mode 100644 (file)
index 0000000..ed5a182
--- /dev/null
@@ -0,0 +1,408 @@
+// Copyright (C) 2018 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 <testutils/io_utils.h>
+#include <testutils/user_context_utils.h>
+#include <yang/translator_config.h>
+#include <yang/yang_models.h>
+#include <yang/tests/yang_configs.h>
+#include <yang/tests/json_configs.h>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <iostream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+
+namespace {
+
+/// @brief Return the difference between two strings
+///
+/// Use the gtest >= 1.8.0 tool which builds the difference between
+/// two vectors of lines.
+///
+/// @param left left string
+/// @param right right string
+/// @return the unified diff between left and right
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+std::string generateDiff(std::string left, std::string right) {
+    std::vector<std::string> left_lines;
+    boost::split(left_lines, left, boost::is_any_of("\n"));
+    std::vector<std::string> right_lines;
+    boost::split(right_lines, right, boost::is_any_of("\n"));
+    using namespace testing::internal;
+    return (edit_distance::CreateUnifiedDiff(left_lines, right_lines));
+}
+#else
+std::string generateDiff(std::string, std::string) {
+    return ("");
+}
+#endif
+
+
+/// @brief Test Fixture class for Yang <-> JSON configs.
+class ConfigTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ConfigTest() {
+        createSession();
+    }
+
+    /// @brief Virtual destructor.
+    virtual ~ConfigTest() {
+        session_.reset();
+        connection_.reset();
+        model_.clear();
+    }
+
+    /// @brief Set model.
+    ///
+    /// @param model The model name.
+    void setModel(const string model) {
+        model_ = model;
+    }
+
+    /// @brief Create session.
+    void createSession() {
+        connection_.reset(new Connection("configs unittests"));
+        session_.reset(new Session(connection_, SR_DS_CANDIDATE));
+    }
+
+    /// @brief Reset session.
+    void resetSession() {
+        session_.reset(new Session(connection_, SR_DS_CANDIDATE));
+    }
+
+    /// @brief Load Yang.
+    ///
+    /// @param tree The Yang tree to load.
+    void load(const YRTree& tree) {
+        YangRepr repr(model_);
+        repr.set(tree, session_);
+    }
+
+    /// @brief Load JSON.
+    ///
+    /// @param json The JSON tree to load.
+    void load(ConstElementPtr json) {
+        TranslatorConfig tc(session_, model_);
+        tc.setConfig(json);
+    }
+
+    /// @brief Load JSON text.
+    ///
+    /// @param config The JSON tree to load in textual format.
+    void load(const string& config) {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = Element::fromJSON(config));
+        load(json);
+    }
+
+    /// @brief Load JSON file.
+    ///
+    /// @param filename The name of the JSON file to load,
+    ConstElementPtr loadFile(const string& filename) {
+        string decommented = isc::test::decommentJSONfile(filename);
+        ConstElementPtr json = Element::fromJSONFile(decommented, true);
+        ::remove(decommented.c_str());
+        load(json);
+        return (json);
+    }
+
+    /// @brief Get Yang.
+    YRTree getYang() {
+        YangRepr repr(model_);
+        return (repr.get(session_));
+    }
+
+    /// @brief Get JSON.
+    ConstElementPtr getJSON() {
+        TranslatorConfig tc(session_, model_);
+        return (tc.getConfig());
+    }
+
+    /// @brief Get JSON text.
+    string getText() {
+        return (isc::data::prettyPrint(getJSON()));
+    }
+
+    /// @brief Verify Yang.
+    ///
+    /// @param expected The expected Yang tree.
+    bool verify(const YRTree& expected) {
+        YangRepr repr(model_);
+        return (repr.verify(expected, session_, cerr));
+    }
+
+    /// @brief Verify JSON.
+    ///
+    /// @param expected The expected JSON tree.
+    bool verify(ConstElementPtr expected) {
+        TranslatorConfig tc(session_, model_);
+        ConstElementPtr content = tc.getConfig();
+        if (isEquivalent(expected, content)) {
+            return (true);
+        }
+        string wanted = prettyPrint(expected);
+        string got = prettyPrint(content);
+        cerr << "Expected:\n" << wanted << "\n"
+             << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+             << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+             << "\n";
+        return (false);
+    }
+
+    /// @brief Verify JSON.
+    ///
+    /// @param expected The expected JSON tree in textual format.
+    bool verify(const string& config) {
+        ConstElementPtr expected;
+        expected= Element::fromJSON(config);
+        return (verify(expected));
+    }
+
+    /// @brief Validate.
+    ///
+    /// @note A tree must be loaded first.
+    ///
+    bool validate() {
+        YangRepr repr(model_);
+        return (repr.validate(session_, cerr));
+    }
+
+    /// @brief The model.
+    string model_;
+
+    /// @brief The sysrepo connection.
+    S_Connection connection_;
+
+    /// @brief The sysrepo session.
+    S_Session session_;
+};
+
+// Check empty config with ietf-dhcpv6-server model.
+TEST_F(ConfigTest, emptyIetf6) {
+    // First set the model.
+    setModel(IETF_DHCPV6_SERVER);
+
+    YRTree tree;
+    ASSERT_NO_THROW(load(tree));
+    EXPECT_TRUE(verify(tree));
+
+    ConstElementPtr json = Element::fromJSON(emptyJson6);
+    EXPECT_TRUE(verify(json));
+    ASSERT_NO_THROW(load(json));
+    EXPECT_TRUE(verify(emptyJson6));
+    EXPECT_TRUE(verify(tree));
+}
+
+// Check empty config with kea-dhcp4-server:config model.
+TEST_F(ConfigTest, emptyKeaDhcp4) {
+    // First set the model.
+    setModel(KEA_DHCP4_SERVER);
+
+    YRTree tree;
+    ASSERT_NO_THROW(load(tree));
+    EXPECT_TRUE(verify(tree));
+
+    ConstElementPtr json = Element::fromJSON(emptyJson4);
+    EXPECT_TRUE(verify(json));
+    ASSERT_NO_THROW(load(json));
+    EXPECT_TRUE(verify(emptyJson4));
+    EXPECT_TRUE(verify(tree));
+}
+
+// Check empty config with kea-dhcp6-server:config model.
+TEST_F(ConfigTest, emptyKeaDhcp6) {
+    // First set the model.
+    setModel(KEA_DHCP6_SERVER);
+
+    YRTree tree;
+    ASSERT_NO_THROW(load(tree));
+    EXPECT_TRUE(verify(tree));
+
+    ConstElementPtr json = Element::fromJSON(emptyJson6);
+    EXPECT_TRUE(verify(json));
+    ASSERT_NO_THROW(load(json));
+    EXPECT_TRUE(verify(emptyJson6));
+    EXPECT_TRUE(verify(tree));
+}
+
+// Check subnet with two pools with ietf-dhcpv6-server model.
+TEST_F(ConfigTest, subnetTwoPoolsIetf6) {
+    // First set the model.
+    setModel(subnetTwoPoolsModelIetf6);
+
+    ASSERT_NO_THROW(load(subnetTwoPoolsTreeIetf6));
+    EXPECT_TRUE(verify(subnetTwoPoolsJson6));
+
+    resetSession();
+
+    ASSERT_NO_THROW(load(subnetTwoPoolsJson6));
+    EXPECT_TRUE(verify(subnetTwoPoolsTreeIetf6));
+
+    cout << "validation is expected to fail: please ignore messages" << endl;
+    EXPECT_FALSE(validate());
+}
+
+// Check subnet with a pool and option data lists with
+// kea-dhcp4-server:config model.
+TEST_F(ConfigTest, subnetOptionsKeaDhcp4) {
+    // First set the model.
+    setModel(subnetOptionsModelKeaDhcp4);
+
+    ASSERT_NO_THROW(load(subnetOptionsTreeKeaDhcp4));
+    EXPECT_TRUE(verify(subnetOptionsJson4));
+
+    resetSession();
+
+    ASSERT_NO_THROW(load(subnetOptionsJson4));
+    EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp4));
+
+    EXPECT_TRUE(validate());
+}
+
+// Check subnet with a pool and option data lists with
+// kea-dhcp6-server:config model.
+TEST_F(ConfigTest, subnetOptionsKeaDhcp6) {
+    // First set the model.
+    setModel(subnetOptionsModelKeaDhcp6);
+
+    ASSERT_NO_THROW(load(subnetOptionsTreeKeaDhcp6));
+    EXPECT_TRUE(verify(subnetOptionsJson6));
+
+    resetSession();
+
+    ASSERT_NO_THROW(load(subnetOptionsJson6));
+    EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp6));
+
+    EXPECT_TRUE(validate());
+}
+
+// Check with timers.
+TEST_F(ConfigTest, subnetTimersIetf6) {
+    // First set the model.
+    setModel(subnetTimersModel);
+
+    ASSERT_NO_THROW(load(subnetTimersIetf6));
+    EXPECT_TRUE(verify(subnetTimersJson6));
+
+    resetSession();
+
+    ASSERT_NO_THROW(load(subnetTimersJson6));
+    EXPECT_TRUE(verify(subnetTimersIetf6));
+}
+
+// Check a ietf-dhcpv6-server configuration which validates.
+TEST_F(ConfigTest, validateIetf6) {
+    // First set the model.
+    setModel(validModelIetf6);
+
+    ASSERT_NO_THROW(load(validTreeIetf6));
+    EXPECT_TRUE(verify(validTreeIetf6));
+
+    EXPECT_TRUE(validate());
+}
+
+// Check Kea4 example files.
+TEST_F(ConfigTest, examples4) {
+    // First set the model.
+    setModel(KEA_DHCP4_SERVER);
+
+    vector<string> examples = {
+        "advanced.json",
+        "all-keys.json",
+        "backends.json",
+        "cassandra.json",
+        "classify.json",
+        "classify2.json",
+        "comments.json",
+        "dhcpv4-over-dhcpv6.json",
+        "hooks.json",
+        "leases-expiration.json",
+        "multiple-options.json",
+        "mysql-reservations.json",
+        "pgsql-reservations.json",
+        "reservations.json",
+        "several-subnets.json",
+        "shared-network.json",
+        "single-subnet.json",
+        "with-ddns.json"
+    };
+    for (string file : examples) {
+        resetSession();
+        string path = string(CFG_EXAMPLES) + "/kea4/" + file;
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = loadFile(path));
+        json = isc::test::moveComments(json);
+        EXPECT_TRUE(verify(json));
+        EXPECT_TRUE(validate());
+    }
+}
+
+// Check Kea6 example files.
+TEST_F(ConfigTest, examples6) {
+    // First set the model.
+    setModel(KEA_DHCP6_SERVER);
+
+    vector<string> examples = {
+        "advanced.json",
+        "all-keys.json",
+        "backends.json",
+        "cassandra.json",
+        "classify.json",
+        "classify2.json",
+        "comments.json",
+        "dhcpv4-over-dhcpv6.json",
+        "duid.json",
+        "hooks.json",
+        "iPXE.json",
+        "leases-expiration.json",
+        "multiple-options.json",
+        "mysql-reservations.json",
+        "pgsql-reservations.json",
+        "reservations.json",
+        "several-subnets.json",
+        "shared-network.json",
+        "simple.json",
+        "softwire46.json",
+        "stateless.json",
+        "with-ddns.json"
+    };
+    for (string file : examples) {
+        resetSession();
+        string path = string(CFG_EXAMPLES) + "/kea6/" + file;
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = loadFile(path));
+        json = isc::test::moveComments(json);
+        EXPECT_TRUE(verify(json));
+        EXPECT_TRUE(validate());
+    }
+}
+
+// Check the example in the design document.
+TEST_F(ConfigTest, designExample) {
+    // First set the model.
+    setModel(designExampleModel);
+
+    ASSERT_NO_THROW(load(designExampleTree));
+    EXPECT_TRUE(verify(designExampleJson));
+
+    resetSession();
+
+    ASSERT_NO_THROW(load(designExampleJson));
+    EXPECT_TRUE(verify(designExampleTree));
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/yang/tests/json_configs.h b/src/lib/yang/tests/json_configs.h
new file mode 100644 (file)
index 0000000..032aef1
--- /dev/null
@@ -0,0 +1,175 @@
+// Copyright (C) 2018 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/.
+
+#ifndef ISC_JSON_CONFIGS_H
+#define ISC_JSON_CONFIGS_H
+
+#include <string>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief Empty DHCPv4 config.
+const std::string emptyJson4 =
+    "{\n"
+    "  \"Dhcp4\": {\n"
+    "  }\n"
+    "}";
+
+/// @brief A DHCPv4 config with one subnet with two pools.
+const std::string subnetTwoPoolsJson4 =
+    "{\n"
+    "  \"Dhcp4\": {\n"
+    "    \"subnet4\": [\n"
+    "      {\n"
+    "        \"id\": 111,\n"
+    "        \"pools\": [\n"
+    "          {\n"
+    "            \"pool\": \"10.0.1.0/24\"\n"
+    "          },\n"
+    "          {\n"
+    "            \"pool\": \"10.0.2.0/24\"\n"
+    "          }\n"
+    "        ],\n"
+    "        \"subnet\": \"10.0.0.0/8\"\n"
+    "      }\n"
+    "    ]\n"
+    "  }\n"
+    "}";
+
+/// @brief A DHCPv6 config with one subnet with two pools and timers.
+const std::string subnetTimersJson6 =
+    "{\n"
+    "  \"Dhcp6\": {\n"
+    "    \"subnet6\": [\n"
+    "      {\n"
+    "        \"id\": 111,\n"
+    "        \"renew-timer\": 1000,\n"
+    "        \"rebind-timer\": 2000,\n"
+    "        \"pools\": [\n"
+    "          {\n"
+    "            \"pool\": \"2001:db8::1:0/112\"\n"
+    "          },\n"
+    "          {\n"
+    "            \"pool\": \"2001:db8::2:0/112\"\n"
+    "          }\n"
+    "        ],\n"
+    "        \"subnet\": \"2001:db8::/48\"\n"
+    "      }\n"
+    "    ]\n"
+    "  }\n"
+    "}";
+
+/// @brief A DHCPv4 subnet with one pool and option data lists.
+const std::string subnetOptionsJson4 =
+    "{\n"
+    "  \"Dhcp4\": {\n"
+    "    \"subnet4\": [\n"
+    "      {\n"
+    "        \"id\": 111,\n"
+    "        \"option-data\": [\n"
+    "          {\n"
+    "            \"code\": 100,\n"
+    "            \"space\": \"dns\",\n"
+    "            \"csv-format\": false,\n"
+    "            \"data\": \"12121212\",\n"
+    "            \"always-send\": false\n"
+    "          }\n"
+    "        ],\n"
+    "        \"pools\": [\n"
+    "          {\n"
+    "            \"pool\": \"10.0.1.0/24\"\n"
+    "          }\n"
+    "        ],\n"
+    "        \"subnet\": \"10.0.0.0/8\"\n"
+    "      }\n"
+    "    ]\n"
+    "  }\n"
+    "}";
+
+/// @brief Empty DHCPv6 config.
+const std::string emptyJson6 =
+    "{\n"
+    "  \"Dhcp6\": {\n"
+    "  }\n"
+    "}";
+
+/// @brief A DHCPv6 config with one subnet with one pool and option data lists.
+const std::string subnetOptionsJson6 =
+    "{\n"
+    "  \"Dhcp6\": {\n"
+    "    \"subnet6\": [\n"
+    "      {\n"
+    "        \"id\": 111,\n"
+    "        \"pools\": [\n"
+    "          {\n"
+    "            \"option-data\": [\n"
+    "              {\n"
+    "                \"code\": 100,\n"
+    "                \"space\": \"dns\",\n"
+    "                \"csv-format\": false,\n"
+    "                \"data\": \"12121212\",\n"
+    "                \"always-send\": false\n"
+    "              }\n"
+    "            ],\n"
+    "            \"pool\": \"2001:db8::1:0/112\"\n"
+    "          }\n"
+    "        ],\n"
+    "        \"subnet\": \"2001:db8::/48\"\n"
+    "      }\n"
+    "    ]\n"
+    "  }\n"
+    "}";
+
+/// @brief A DHCPv6 config with one subnet with two pools.
+const std::string subnetTwoPoolsJson6 =
+    "{\n"
+    "  \"Dhcp6\": {\n"
+    "    \"subnet6\": [\n"
+    "      {\n"
+    "        \"id\": 111,\n"
+    "        \"pools\": [\n"
+    "          {\n"
+    "            \"pool\": \"2001:db8::1:0/112\"\n"
+    "          },\n"
+    "          {\n"
+    "            \"pool\": \"2001:db8::2:0/112\"\n"
+    "          }\n"
+    "        ],\n"
+    "        \"subnet\": \"2001:db8::/48\"\n"
+    "      }\n"
+    "    ]\n"
+    "  }\n"
+    "}";
+
+/// @brief Example from the design document.
+const std::string designExampleJson =
+    "{\n"
+    "  \"Dhcp6\": {\n"
+    "    \"subnet6\": [\n"
+    "      {\n"
+    "        \"id\": 1,\n"
+    "        \"subnet\": \"2001:db8:20:b00::/56\",\n"
+    "        \"user-context\": { \"description\": \"example\" },\n"
+    "        \"pools\": [ ],\n"
+    "        \"pd-pools\": [\n"
+    "          {\n"
+    "            \"prefix\": \"2001:db8:20:b00::\",\n"
+    "            \"prefix-len\": 57\n"
+//  "            \"delegated-len\": 57\n"
+    "          }\n"
+    "        ]\n"
+    "      }\n"
+    "    ]\n"
+    "  }\n"
+    "}";
+
+}; // end of namespace isc::yang::test
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_JSON_CONFIGS_H
index 204120d0058a24a6c17b27628629a7b060e0c5d9..6178cacb66cb6dfcf85a81ef6df32187d40a837f 100644 (file)
@@ -215,7 +215,7 @@ void sanityCheckConfig(const std::string& model, const YRTree& tree) {
 // defined in yang_configs.h are sane.
 TEST(YangReprTest, verifyConfigs) {
     for (auto x : test_configs) {
-        cout << "Testing tree for model " << x.first << endl;
+        //cout << "Testing tree for model " << x.first << endl;
         sanityCheckConfig(x.first, x.second);
     }
 }
diff --git a/src/lib/yang/translator_config.cc b/src/lib/yang/translator_config.cc
new file mode 100644 (file)
index 0000000..04a7ae8
--- /dev/null
@@ -0,0 +1,908 @@
+// Copyright (C) 2018 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 <yang/translator_config.h>
+#include <yang/adaptor_config.h>
+#include <yang/yang_models.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace yang {
+
+TranslatorConfig::TranslatorConfig(S_Session session, const string& model)
+    : TranslatorBasic(session),
+      TranslatorControlSocket(session, model),
+      TranslatorDatabase(session, model),
+      TranslatorDatabases(session, model),
+      TranslatorOptionData(session, model),
+      TranslatorOptionDataList(session, model),
+      TranslatorOptionDef(session, model),
+      TranslatorOptionDefList(session, model),
+      TranslatorClass(session, model),
+      TranslatorClasses(session, model),
+      TranslatorPool(session, model),
+      TranslatorPools(session, model),
+      TranslatorPdPool(session, model),
+      TranslatorPdPools(session, model),
+      TranslatorHost(session, model),
+      TranslatorHosts(session, model),
+      TranslatorSubnet(session, model),
+      TranslatorSubnets(session, model),
+      TranslatorSharedNetwork(session, model),
+      TranslatorSharedNetworks(session, model),
+      TranslatorLogger(session, model),
+      TranslatorLoggers(session, model),
+      model_(model) {
+}
+
+TranslatorConfig::~TranslatorConfig() {
+}
+
+ElementPtr
+TranslatorConfig::getConfig() {
+    try {
+        if (model_ == IETF_DHCPV6_SERVER) {
+            return (getConfigIetf6());
+        } else if (model_ == KEA_DHCP4_SERVER) {
+            return (getConfigKea4());
+        } else if (model_ == KEA_DHCP6_SERVER) {
+            return (getConfigKea6());
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError, "sysrepo error getting config: " << ex.what());
+    }
+    isc_throw(NotImplemented,
+              "getConfig not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorConfig::getConfigIetf6() {
+    ElementPtr result = Element::createMap();
+    ElementPtr dhcp6 = Element::createMap();
+    result->set("Dhcp6", dhcp6);
+    string xpath = "/" + model_ + ":server/server-config";
+    ConstElementPtr ranges = getSubnets(xpath + "/network-ranges");
+    if (ranges && (ranges->size() > 0)) {
+        dhcp6->set("subnet6", ranges);
+    }
+    // Skip everything else.
+    return (result);
+}
+
+ElementPtr
+TranslatorConfig::getConfigKea4() {
+    ElementPtr result = Element::createMap();
+    result->set("Dhcp4", getServerKeaDhcp4());
+    ConstElementPtr logging = getServerKeaLogging();
+    if (logging && (logging->size() > 0)) {
+        result->set("Logging", logging);
+    }
+    return (result);
+}
+
+ElementPtr
+TranslatorConfig::getConfigKea6() {
+    ElementPtr result = Element::createMap();
+    result->set("Dhcp6", getServerKeaDhcp6());
+    ConstElementPtr logging = getServerKeaLogging();
+    if (logging && (logging->size() > 0)) {
+        result->set("Logging", logging);
+    }
+    return (result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaDhcpCommon(const string& xpath) {
+    ElementPtr result = Element::createMap();
+    ConstElementPtr valid = getItem(xpath + "/valid-lifetime");
+    if (valid) {
+        result->set("valid-lifetime", valid);
+    }
+    ConstElementPtr renew = getItem(xpath + "/renew-timer");
+    if (renew) {
+        result->set("renew-timer", renew);
+    }
+    ConstElementPtr rebind = getItem(xpath + "/rebind-timer");
+    if (rebind) {
+        result->set("rebind-timer", rebind);
+    }
+    ConstElementPtr period = getItem(xpath + "/decline-probation-period");
+    if (period) {
+        result->set("decline-probation-period", period);
+    }
+    ConstElementPtr networks = getSharedNetworks(xpath + "/shared-networks");
+    if (networks && (networks->size() > 0)) {
+        result->set("shared-networks", networks);
+    }
+    ConstElementPtr classes = getClasses(xpath + "/client-classes");
+    if (classes && (classes->size() > 0)) {
+        result->set("client-classes", classes);
+    }
+    ConstElementPtr database = getDatabase(xpath + "/lease-database");
+    if (database) {
+        result->set("lease-database", database);
+    }
+    ConstElementPtr databases = getDatabases(xpath + "/hosts-databases");
+    if (databases && (databases->size() > 0)) {
+        result->set("hosts-databases", databases);
+    }
+    ConstElementPtr host_ids =
+        getItems(xpath + "/host-reservation-identifiers");
+    if (host_ids) {
+        result->set("host-reservation-identifiers", host_ids);
+    }
+    ConstElementPtr defs = getOptionDefList(xpath + "/option-def-list");
+    if (defs && (defs->size() > 0)) {
+        result->set("option-def", defs);
+    }
+    ConstElementPtr options = getOptionDataList(xpath + "/option-data-list");
+    if (options && (options->size() > 0)) {
+        result->set("option-data", options);
+    }
+    S_Iter_Value iter = getIter(xpath + "/hooks-libraries/*");
+    if (iter) {
+        ElementPtr hook_libs = Element::createList();
+        for (;;) {
+            const string& lib = getNext(iter);
+            if (lib.empty()) {
+                break;
+            }
+            ElementPtr hook_lib = Element::createMap();
+            ConstElementPtr name = getItem(lib + "/library");
+            if (name) {
+                hook_lib->set("library", name);
+                ConstElementPtr params = getItem(lib + "/parameters");
+                if (params) {
+                    string parameters = params->stringValue();
+                    if (!parameters.empty()) {
+                        hook_lib->set("parameters",
+                                      Element::fromJSON(parameters));
+                    }
+                }
+                hook_libs->add(hook_lib);
+            }
+        }
+        if (hook_libs->size() > 0) {
+            result->set("hooks-libraries", hook_libs);
+        }
+    }
+    ElementPtr expired = Element::createMap();
+    ConstElementPtr reclaim =
+        getItem(xpath + "/expired-leases-processing/reclaim-timer-wait-time");
+    if (reclaim) {
+        expired->set("reclaim-timer-wait-time", reclaim);
+    }
+    ConstElementPtr flush =
+        getItem(xpath + "/expired-leases-processing/flush-reclaimed-timer-wait-time");
+    if (flush) {
+        expired->set("flush-reclaimed-timer-wait-time", flush);
+    }
+    ConstElementPtr hold =
+        getItem(xpath + "/expired-leases-processing/hold-reclaimed-time");
+    if (hold) {
+        expired->set("hold-reclaimed-time", hold);
+    }
+    ConstElementPtr max_leases =
+        getItem(xpath + "/expired-leases-processing/max-reclaim-leases");
+    if (max_leases) {
+        expired->set("max-reclaim-leases", max_leases);
+    }
+    ConstElementPtr max_time =
+        getItem(xpath + "/expired-leases-processing/max-reclaim-time");
+    if (max_time) {
+        expired->set("max-reclaim-time", max_time);
+    }
+    ConstElementPtr unwarned =
+        getItem(xpath + "/expired-leases-processing/unwarned-reclaim-cycles");
+    if (unwarned) {
+        expired->set("unwarned-reclaim-cycles", unwarned);
+    }
+    if (expired->size() > 0) {
+        result->set("expired-leases-processing", expired);
+    }
+    ConstElementPtr port = getItem(xpath + "/dhcp4o6-port");
+    if (port) {
+        result->set("dhcp4o6-port", port);
+    }
+    ConstElementPtr socket = getControlSocket(xpath + "/control-socket");
+    if (socket) {
+        result->set("control-socket", socket);
+    }
+    ElementPtr ddns = Element::createMap();
+    ConstElementPtr enable = getItem(xpath + "/dhcp-ddns/enable-updates");
+    if (enable) {
+        ddns->set("enable-updates", enable);
+    }
+    ConstElementPtr suffix = getItem(xpath + "/dhcp-ddns/qualifying-suffix");
+    if (suffix) {
+        ddns->set("qualifying-suffix", suffix);
+    }
+    ConstElementPtr server_ip = getItem(xpath + "/dhcp-ddns/server-ip");
+    if (server_ip) {
+        ddns->set("server-ip", server_ip);
+    }
+    ConstElementPtr server_port = getItem(xpath + "/dhcp-ddns/server-port");
+    if (server_port) {
+        ddns->set("server-port", server_port);
+    }
+    ConstElementPtr sender_ip = getItem(xpath + "/dhcp-ddns/sender-ip");
+    if (sender_ip) {
+        ddns->set("sender-ip", sender_ip);
+    }
+    ConstElementPtr sender_port = getItem(xpath + "/dhcp-ddns/sender-port");
+    if (sender_port) {
+        ddns->set("sender-port", sender_port);
+    }
+    ConstElementPtr queue = getItem(xpath + "/dhcp-ddns/max-queue-size");
+    if (queue) {
+        ddns->set("max-queue-size", queue);
+    }
+    ConstElementPtr protocol = getItem(xpath + "/dhcp-ddns/ncr-protocol");
+    if (protocol) {
+        ddns->set("ncr-protocol", protocol);
+    }
+    ConstElementPtr format = getItem(xpath + "/dhcp-ddns/ncr-format");
+    if (format) {
+        ddns->set("ncr-format", format);
+    }
+    ConstElementPtr always = getItem(xpath + "/dhcp-ddns/always-include-fqdn");
+    if (always) {
+        ddns->set("always-include-fqdn", always);
+    }
+    ConstElementPtr no_up = getItem(xpath + "/dhcp-ddns/override-no-update");
+    if (no_up) {
+        ddns->set("override-no-update", no_up);
+    }
+    ConstElementPtr client =
+        getItem(xpath + "/dhcp-ddns/override-client-update");
+    if (client) {
+        ddns->set("override-client-update", client);
+    }
+    ConstElementPtr replace =
+        getItem(xpath + "/dhcp-ddns/replace-client-name");
+    if (replace) {
+        ddns->set("replace-client-name", replace);
+    }
+    ConstElementPtr generated = getItem(xpath + "/dhcp-ddns/generated-prefix");
+    if (generated) {
+        ddns->set("generated-prefix", generated);
+    }
+    ConstElementPtr char_set = getItem(xpath + "/dhcp-ddns/hostname-char-set");
+    if (char_set) {
+        ddns->set("hostname-char-set", char_set);
+    }
+    ConstElementPtr char_repl =
+        getItem(xpath + "/dhcp-ddns/hostname-char-replacement");
+    if (char_repl) {
+        ddns->set("hostname-char-replacement", char_repl);
+    }
+    ConstElementPtr context = getItem(xpath + "/dhcp-ddns/user-context");
+    if (context) {
+        ddns->set("user-context", Element::fromJSON(context->stringValue()));
+    }
+    if (ddns->size() > 0) {
+        result->set("dhcp-ddns", ddns);
+    }
+    context = getItem(xpath + "/user-context");
+    if (context) {
+        result->set("user-context", Element::fromJSON(context->stringValue()));
+    }
+    ConstElementPtr checks = getItem(xpath + "/sanity-checks/lease-checks");
+    if (checks) {
+        ElementPtr sanity = Element::createMap();
+        sanity->set("lease-checks", checks);
+        result->set("sanity-checks", sanity);
+    }
+    ConstElementPtr hosts = getHosts(xpath + "/reservations");
+    if (hosts && (hosts->size() > 0)) {
+        result->set("reservations", hosts);
+    }
+    return (result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaDhcp4() {
+    string xpath = "/kea-dhcp4-server:config";
+    ElementPtr result = getServerKeaDhcpCommon(xpath);
+    ConstElementPtr subnets = getSubnets(xpath + "/subnet4");
+    if (subnets && (subnets->size() > 0)) {
+        result->set("subnet4", subnets);
+    }
+    ElementPtr if_config = Element::createMap();
+    ConstElementPtr ifs = getItems(xpath + "/interfaces-config/interfaces");
+    if (ifs && (ifs->size() > 0)) {
+        if_config->set("interfaces", ifs);
+    }
+    ConstElementPtr ds_type =
+        getItem(xpath + "/interfaces-config/dhcp-socket-type");
+    if (ds_type) {
+        if_config->set("dhcp-socket-type", ds_type);
+    }
+    ConstElementPtr out_if =
+        getItem(xpath + "/interfaces-config/outbound-interface");
+    if (out_if) {
+        if_config->set("outbound-interface", out_if);
+    }
+    ConstElementPtr redetect =
+        getItem(xpath + "/interfaces-config/re-detect");
+    if (redetect) {
+        if_config->set("re-detect", redetect);
+    }
+    ConstElementPtr context =
+        getItem(xpath + "/interfaces-config/user-context");
+    if (context) {
+        if_config->set("user-context",
+                       Element::fromJSON(context->stringValue()));
+    }
+    if (if_config->size() > 0) {
+        result->set("interfaces-config", if_config);
+    }
+    ConstElementPtr echo = getItem(xpath + "/echo-client-id");
+    if (echo) {
+        result->set("echo-client-id", echo);
+    }
+    ConstElementPtr match = getItem(xpath + "/match-client-id");
+    if (match) {
+        result->set("match-client-id", match);
+    }
+    ConstElementPtr next = getItem(xpath + "/next-server");
+    if (next) {
+        result->set("next-server", next);
+    }
+    ConstElementPtr hostname = getItem(xpath + "/server-hostname");
+    if (hostname) {
+        result->set("server-hostname", hostname);
+    }
+    ConstElementPtr boot = getItem(xpath + "/boot-file-name");
+    if (boot) {
+        result->set("boot-file-name", boot);
+    }
+    return (result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaDhcp6() {
+    string xpath = "/kea-dhcp6-server:config";
+    ElementPtr result = getServerKeaDhcpCommon(xpath);
+    ConstElementPtr preferred = getItem(xpath + "/preferred-lifetime");
+    if (preferred) {
+        result->set("preferred-lifetime", preferred);
+    }
+    ConstElementPtr subnets = getSubnets(xpath + "/subnet6");
+    if (subnets && (subnets->size() > 0)) {
+        result->set("subnet6", subnets);
+    }
+    ElementPtr if_config = Element::createMap();
+    ConstElementPtr ifs = getItems(xpath + "/interfaces-config/interfaces");
+    if (ifs && (ifs->size() > 0)) {
+        if_config->set("interfaces", ifs);
+    }
+    ConstElementPtr redetect =
+        getItem(xpath + "/interfaces-config/re-detect");
+    if (redetect) {
+        if_config->set("re-detect", redetect);
+    }
+    ConstElementPtr context =
+        getItem(xpath + "/interfaces-config/user-context");
+    if (context) {
+        if_config->set("user-context",
+                       Element::fromJSON(context->stringValue()));
+    }
+    if (if_config->size() > 0) {
+        result->set("interfaces-config", if_config);
+    }
+    ConstElementPtr relay = getItems(xpath + "/relay-supplied-options");
+    if (relay) {
+        result->set("relay-supplied-options", relay);
+    }
+    ConstElementPtr macs = getItems(xpath + "/mac-sources");
+    if (macs) {
+        result->set("mac-sources", macs);
+    }
+    ElementPtr server_id = Element::createMap();
+    ConstElementPtr id_type = getItem(xpath + "/server-id/type");
+    if (id_type) {
+        server_id->set("type", id_type);
+    }
+    ConstElementPtr id_id = getItem(xpath + "/server-id/identifier");
+    if (id_id) {
+        server_id->set("identifier", id_id);
+    }
+    ConstElementPtr id_time = getItem(xpath + "/server-id/time");
+    if (id_time) {
+        server_id->set("time", id_time);
+    }
+    ConstElementPtr id_htype = getItem(xpath + "/server-id/htype");
+    if (id_htype) {
+        server_id->set("htype", id_htype);
+    }
+    ConstElementPtr id_ent_id = getItem(xpath + "/server-id/enterprise-id");
+    if (id_ent_id) {
+        server_id->set("enterprise-id", id_ent_id);
+    }
+    ConstElementPtr id_persist = getItem(xpath + "/server-id/persist");
+    if (id_persist) {
+        server_id->set("persist", id_persist);
+    }
+    context = getItem(xpath + "/server-id/user-context");
+    if (context) {
+        server_id->set("user-context",
+                       Element::fromJSON(context->stringValue()));
+    }
+    if (server_id->size() > 0) {
+        result->set("server-id", server_id);
+    }
+    return (result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaLogging() {
+    string xpath = "/" + model_ + ":logging";
+    ElementPtr result = Element::createMap();
+    ConstElementPtr loggers = getLoggers(xpath + "/loggers");
+    if (loggers && (loggers->size() > 0)) {
+        result->set("loggers", loggers);
+    }
+    return (result);
+}
+
+void
+TranslatorConfig::setConfig(ConstElementPtr elem) {
+    try {
+        if (model_ == IETF_DHCPV6_SERVER) {
+            if (elem) {
+                AdaptorConfig::preProcess6(elem);
+                setConfigIetf6(elem);
+            } else {
+                delConfigIetf6();
+            }
+        } else if (model_ == KEA_DHCP4_SERVER) {
+            if (elem) {
+                AdaptorConfig::preProcess4(elem);
+                setConfigKea4(elem);
+            } else {
+                delConfigKea();
+            }
+        } else if (model_ == KEA_DHCP6_SERVER) {
+            if (elem) {
+                AdaptorConfig::preProcess6(elem);
+                setConfigKea6(elem);
+            } else {
+                delConfigKea();
+            }
+        } else {
+            isc_throw(NotImplemented,
+                      "setConfig not implemented for the model: " << model_);
+        }
+    } catch (const sysrepo_exception& ex) {
+        isc_throw(SysrepoError,
+                  "sysrepo error setting config '" << elem->str()
+                  << "': " << ex.what());
+    }
+}
+
+void
+TranslatorConfig::delConfigIetf6() {
+    delItem("/" + model_ + ":server");
+}
+
+void
+TranslatorConfig::setConfigIetf6(ConstElementPtr elem) {
+    string xpath = "/" + model_ + ":server/server-config";
+    ConstElementPtr dhcp6 = elem->get("Dhcp6");
+    if (!dhcp6) {
+        isc_throw(BadValue, "no Dhcp6 entry in " << elem->str());
+    }
+    ConstElementPtr ranges = dhcp6->get("subnet6");
+    if (ranges && (ranges->size() > 0)) {
+        setSubnets(xpath + "/network-ranges", ranges);
+    }
+    // Skip everything else.
+}
+
+void
+TranslatorConfig::delConfigKea() {
+    delItem("/" + model_ + ":config");
+    delItem("/" + model_ + ":logging");
+}
+
+void
+TranslatorConfig::setConfigKea4(ConstElementPtr elem) {
+    ConstElementPtr dhcp = elem->get("Dhcp4");
+    if (dhcp) {
+        setServerKeaDhcp4(dhcp);
+    }
+    ConstElementPtr logging = elem->get("Logging");
+    if (logging) {
+        setServerKeaLogging(logging);
+    }
+}
+
+void
+TranslatorConfig::setConfigKea6(ConstElementPtr elem) {
+    ConstElementPtr dhcp = elem->get("Dhcp6");
+    if (dhcp) {
+        setServerKeaDhcp6(dhcp);
+    }
+    ConstElementPtr logging = elem->get("Logging");
+    if (logging) {
+        setServerKeaLogging(logging);
+    }
+}
+
+void
+TranslatorConfig::setServerKeaDhcpCommon(const string& xpath,
+                                         ConstElementPtr elem) {
+    ConstElementPtr valid = elem->get("valid-lifetime");
+    if (valid) {
+        setItem(xpath + "/valid-lifetime", valid, SR_UINT32_T);
+    }
+    ConstElementPtr renew = elem->get("renew-timer");
+    if (renew) {
+        setItem(xpath + "/renew-timer", renew, SR_UINT32_T);
+    }
+    ConstElementPtr rebind = elem->get("rebind-timer");
+    if (rebind) {
+        setItem(xpath + "/rebind-timer", rebind, SR_UINT32_T);
+    }
+    ConstElementPtr period = elem->get("decline-probation-period");
+    if (period) {
+        setItem(xpath + "/decline-probation-period", period, SR_UINT32_T);
+    }
+    ConstElementPtr networks = elem->get("shared-networks");
+    if (networks) {
+        setSharedNetworks(xpath + "/shared-networks", networks);
+    }
+    ConstElementPtr classes = elem->get("client-classes");
+    if (classes && (classes->size() > 0)) {
+        setClasses(xpath + "/client-classes", classes);
+    }
+    ConstElementPtr database = elem->get("lease-database");
+    if (database) {
+        setDatabase(xpath + "/lease-database", database);
+    }
+    ConstElementPtr databases = elem->get("hosts-databases");
+    if (databases) {
+        if (databases->size() > 0) {
+            setDatabases(xpath + "/hosts-databases", databases);
+        }
+    } else {
+        database = elem->get("hosts-database");
+        if (database) {
+            ElementPtr list = Element::createList();
+            list->add(copy(database));
+            setDatabases(xpath + "/hosts-databases", list);
+        }
+    }
+    ConstElementPtr host_ids = elem->get("host-reservation-identifiers");
+    if (host_ids) {
+        for (ConstElementPtr id : host_ids->listValue()) {
+            setItem(xpath + "/host-reservation-identifiers", id, SR_ENUM_T);
+        }
+    }
+    ConstElementPtr defs = elem->get("option-def");
+    if (defs && (defs->size() > 0)) {
+        setOptionDefList(xpath + "/option-def-list", defs);
+    }
+    ConstElementPtr options = elem->get("option-data");
+    if (options && (options->size() > 0)) {
+        setOptionDataList(xpath + "/option-data-list", options);
+    }
+    ConstElementPtr hook_libs = elem->get("hooks-libraries");
+    if (hook_libs) {
+        for (ConstElementPtr lib : hook_libs->listValue()) {
+            ConstElementPtr name = lib->get("library");
+            if (!name) {
+                continue;
+            }
+            ostringstream hook_lib;
+            hook_lib << xpath << "/hooks-libraries/hook-library[library='"
+                     << name->stringValue() << "']";
+            ConstElementPtr params = lib->get("parameters");
+            if (params) {
+                hook_lib << "/parameters";
+                setItem(hook_lib.str(), Element::create(params->str()),
+                        SR_STRING_T);
+            } else {
+                ConstElementPtr list = Element::createList();
+                setItem(hook_lib.str(), list, SR_LIST_T);
+            }
+        }
+    }
+    ConstElementPtr expired = elem->get("expired-leases-processing");
+    if (expired) {
+        ConstElementPtr reclaim = expired->get("reclaim-timer-wait-time");
+        if (reclaim) {
+            setItem(xpath + "/expired-leases-processing/reclaim-timer-wait-time",
+                    reclaim, SR_UINT32_T);
+        }
+        ConstElementPtr flush =
+            expired->get("flush-reclaimed-timer-wait-time");
+        if (flush) {
+            setItem(xpath + "/expired-leases-processing/flush-reclaimed-timer-wait-time",
+                    flush, SR_UINT32_T);
+        }
+        ConstElementPtr hold = expired->get("hold-reclaimed-time");
+        if (hold) {
+            setItem(xpath + "/expired-leases-processing/hold-reclaimed-time",
+                    hold, SR_UINT32_T);
+        }
+        ConstElementPtr max_leases = expired->get("max-reclaim-leases");
+        if (max_leases) {
+            setItem(xpath + "/expired-leases-processing/max-reclaim-leases",
+                    max_leases, SR_UINT32_T);
+        }
+        ConstElementPtr max_time = expired->get("max-reclaim-time");
+        if (max_time) {
+            setItem(xpath + "/expired-leases-processing/max-reclaim-time",
+                    max_time, SR_UINT32_T);
+        }
+        ConstElementPtr unwarned = expired->get("unwarned-reclaim-cycles");
+        if (unwarned) {
+            setItem(xpath + "/expired-leases-processing/unwarned-reclaim-cycles",
+                    unwarned, SR_UINT32_T);
+        }
+    }
+    ConstElementPtr port = elem->get("dhcp4o6-port");
+    if (port) {
+        setItem(xpath + "/dhcp4o6-port", port, SR_UINT16_T);
+    }
+    ConstElementPtr socket = elem->get("control-socket");
+    if (socket) {
+        setControlSocket(xpath + "/control-socket", socket);
+    }
+    ConstElementPtr ddns = elem->get("dhcp-ddns");
+    if (ddns) {
+        ConstElementPtr enable = ddns->get("enable-updates");
+        if (enable) {
+            setItem(xpath + "/dhcp-ddns/enable-updates", enable, SR_BOOL_T);
+        }
+        ConstElementPtr suffix = ddns->get("qualifying-suffix");
+        if (suffix) {
+            setItem(xpath + "/dhcp-ddns/qualifying-suffix", suffix,
+                    SR_STRING_T);
+        }
+        ConstElementPtr server_ip = ddns->get("server-ip");
+        if (server_ip) {
+            setItem(xpath + "/dhcp-ddns/server-ip", server_ip, SR_STRING_T);
+        }
+        ConstElementPtr server_port = ddns->get("server-port");
+        if (server_port) {
+            setItem(xpath + "/dhcp-ddns/server-port", server_port,
+                    SR_UINT16_T);
+        }
+        ConstElementPtr sender_ip = ddns->get("sender-ip");
+        if (sender_ip) {
+            setItem(xpath + "/dhcp-ddns/sender-ip", sender_ip, SR_STRING_T);
+        }
+        ConstElementPtr sender_port = ddns->get("sender-port");
+        if (sender_port) {
+            setItem(xpath + "/dhcp-ddns/sender-port", sender_port,
+                    SR_UINT16_T);
+        }
+        ConstElementPtr queue = ddns->get("max-queue-size");
+        if (queue) {
+            setItem(xpath + "/dhcp-ddns/max-queue-size", queue, SR_UINT32_T);
+        }
+        ConstElementPtr protocol = ddns->get("ncr-protocol");
+        if (protocol) {
+            setItem(xpath + "/dhcp-ddns/ncr-protocol", protocol, SR_ENUM_T);
+        }
+        ConstElementPtr format = ddns->get("ncr-format");
+        if (format) {
+            setItem(xpath + "/dhcp-ddns/ncr-format", format, SR_ENUM_T);
+        }
+        ConstElementPtr always = ddns->get("always-include-fqdn");
+        if (always) {
+            setItem(xpath + "/dhcp-ddns/always-include-fqdn", always,
+                    SR_BOOL_T);
+        }
+        ConstElementPtr no_up = ddns->get("override-no-update");
+        if (no_up) {
+            setItem(xpath + "/dhcp-ddns/override-no-update", no_up, SR_BOOL_T);
+        }
+        ConstElementPtr client = ddns->get("override-client-update");
+        if (client) {
+            setItem(xpath + "/dhcp-ddns/override-client-update", client,
+                    SR_BOOL_T);
+        }
+        ConstElementPtr replace = ddns->get("replace-client-name");
+        if (replace) {
+            setItem(xpath + "/dhcp-ddns/replace-client-name", replace,
+                    SR_ENUM_T);
+        }
+        ConstElementPtr generated = ddns->get("generated-prefix");
+        if (generated) {
+            setItem(xpath + "/dhcp-ddns/generated-prefix", generated,
+                    SR_STRING_T);
+        }
+        ConstElementPtr char_set = ddns->get("hostname-char-set");
+        if (char_set) {
+            setItem(xpath + "/dhcp-ddns/hostname-char-set", char_set,
+                    SR_STRING_T);
+        }
+        ConstElementPtr char_repl = ddns->get("hostname-char-replacement");
+        if (char_repl) {
+            setItem(xpath + "/dhcp-ddns/hostname-char-replacement", char_repl,
+                    SR_STRING_T);
+        }
+        ConstElementPtr context = Adaptor::getContext(ddns);
+        if (context) {
+            ConstElementPtr repr = Element::create(context->str());
+            setItem(xpath + "/dhcp-ddns/user-context", repr, SR_STRING_T);
+        }
+    }
+    ConstElementPtr context = Adaptor::getContext(elem);
+    if (context) {
+        ConstElementPtr repr = Element::create(context->str());
+        setItem(xpath + "/user-context", repr, SR_STRING_T);
+    }
+    ConstElementPtr sanity = elem->get("sanity-checks");
+    if (sanity) {
+        ConstElementPtr checks = sanity->get("lease-checks");
+        if (checks) {
+            setItem(xpath + "/sanity-checks/lease-checks", checks, SR_ENUM_T);
+        }
+    }
+    ConstElementPtr hosts = elem->get("reservations");
+    if (hosts && (hosts->size() > 0)) {
+        setHosts(xpath + "/reservations", hosts);
+    }
+}
+
+void
+TranslatorConfig::setServerKeaDhcp4(ConstElementPtr elem) {
+    string xpath = "/kea-dhcp4-server:config";
+    setServerKeaDhcpCommon(xpath, elem);
+    ConstElementPtr subnets = elem->get("subnet4");
+    if (subnets) {
+        setSubnets(xpath + "/subnet4", subnets);
+    }
+    ConstElementPtr if_config = elem->get("interfaces-config");
+    if (if_config) {
+        ConstElementPtr ifs = if_config->get("interfaces");
+        if (ifs && (ifs->size() > 0)) {
+            for (ConstElementPtr intf : ifs->listValue()) {
+                setItem(xpath + "/interfaces-config/interfaces",
+                        intf, SR_STRING_T);
+            }
+        }
+        ConstElementPtr ds_type = if_config->get("dhcp-socket-type");
+        if (ds_type) {
+            setItem(xpath + "/interfaces-config/dhcp-socket-type",
+                    ds_type, SR_ENUM_T);
+        }
+        ConstElementPtr out_if = if_config->get("outbound-interface");
+        if (out_if) {
+            setItem(xpath + "/interfaces-config/outbound-interface",
+                    out_if, SR_ENUM_T);
+        }
+        ConstElementPtr redetect = if_config->get("re-detect");
+        if (redetect) {
+            setItem(xpath + "/interfaces-config/re-detect",
+                    redetect, SR_BOOL_T);
+        }
+        ConstElementPtr context = Adaptor::getContext(if_config);
+        if (context) {
+            setItem(xpath + "/interfaces-config/user-context",
+                    Element::create(context->str()), SR_STRING_T);
+        }
+    }
+    ConstElementPtr echo = elem->get("echo-client-id");
+    if (echo) {
+        setItem(xpath + "/echo-client-id", echo, SR_BOOL_T);
+    }
+    ConstElementPtr match = elem->get("match-client-id");
+    if (match) {
+        setItem(xpath + "/match-client-id", match, SR_BOOL_T);
+    }
+    ConstElementPtr next = elem->get("next-server");
+    if (next) {
+        setItem(xpath + "/next-server", next, SR_STRING_T);
+    }
+    ConstElementPtr hostname = elem->get("server-hostname");
+    if (hostname) {
+        setItem(xpath + "/server-hostname", hostname, SR_STRING_T);
+    }
+    ConstElementPtr boot = elem->get("boot-file-name");
+    if (boot) {
+        setItem(xpath + "/boot-file-name", boot, SR_STRING_T);
+    }
+}
+
+void
+TranslatorConfig::setServerKeaDhcp6(ConstElementPtr elem) {
+    string xpath = "/kea-dhcp6-server:config";
+    setServerKeaDhcpCommon(xpath, elem);
+    ConstElementPtr preferred = elem->get("preferred-lifetime");
+    if (preferred) {
+        setItem(xpath + "/preferred-lifetime", preferred, SR_UINT32_T);
+    }
+    ConstElementPtr subnets = elem->get("subnet6");
+    if (subnets) {
+        setSubnets(xpath + "/subnet6", subnets);
+    }
+    ConstElementPtr if_config = elem->get("interfaces-config");
+    if (if_config) {
+        ConstElementPtr ifs = if_config->get("interfaces");
+        if (ifs && (ifs->size() > 0)) {
+            for (ConstElementPtr intf : ifs->listValue()) {
+                setItem(xpath + "/interfaces-config/interfaces",
+                        intf, SR_STRING_T);
+            }
+        }
+        ConstElementPtr redetect = if_config->get("re-detect");
+        if (redetect) {
+            setItem(xpath + "/interfaces-config/re-detect",
+                    redetect, SR_BOOL_T);
+        }
+        ConstElementPtr context = Adaptor::getContext(if_config);
+        if (context) {
+            setItem(xpath + "/interfaces-config/user-context",
+                    Element::create(context->str()), SR_STRING_T);
+        }
+    }
+    ConstElementPtr relay = elem->get("relay-supplied-options");
+    if (relay) {
+        for (ConstElementPtr addr : relay->listValue()) {
+            setItem(xpath + "/relay-supplied-options", addr, SR_STRING_T);
+        }
+    }
+    ConstElementPtr macs = elem->get("mac-sources");
+    if (macs) {
+        for (ConstElementPtr source : macs->listValue()) {
+            setItem(xpath + "/mac-sources", source, SR_STRING_T);
+        }
+    }
+    ConstElementPtr server_id = elem->get("server-id");
+    if (server_id) {
+        ConstElementPtr id_type = server_id->get("type");
+        if (id_type) {
+            setItem(xpath + "/server-id/type", id_type, SR_ENUM_T);
+        }
+        ConstElementPtr id_id = server_id->get("identifier");
+        if (id_id) {
+            setItem(xpath + "/server-id/identifier", id_id, SR_STRING_T);
+        }
+        ConstElementPtr id_time = server_id->get("time");
+        if (id_time) {
+            setItem(xpath + "/server-id/time", id_time, SR_UINT32_T);
+        }
+        ConstElementPtr id_htype = server_id->get("htype");
+        if (id_htype) {
+            setItem(xpath + "/server-id/htype", id_htype, SR_UINT16_T);
+        }
+        ConstElementPtr id_ent_id = server_id->get("enterprise-id");
+        if (id_ent_id) {
+            setItem(xpath + "/server-id/enterprise-id", id_ent_id,
+                    SR_UINT32_T);
+        }
+        ConstElementPtr id_persist = server_id->get("persist");
+        if (id_persist) {
+            setItem(xpath + "/server-id/persist", id_persist, SR_BOOL_T);
+        }
+        ConstElementPtr context = Adaptor::getContext(server_id);
+        if (context) {
+            ConstElementPtr repr = Element::create(context->str());
+            setItem(xpath + "/server-id/user-context", repr, SR_STRING_T);
+        }
+    }
+}
+
+void
+TranslatorConfig::setServerKeaLogging(ConstElementPtr elem) {
+    string xpath = "/" + model_ + ":logging";
+    ConstElementPtr loggers = elem->get("loggers");
+    if (loggers) {
+        setLoggers(xpath + "/loggers", loggers);
+    }
+}
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
diff --git a/src/lib/yang/translator_config.h b/src/lib/yang/translator_config.h
new file mode 100644 (file)
index 0000000..b638d79
--- /dev/null
@@ -0,0 +1,308 @@
+// Copyright (C) 2018 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/.
+
+#ifndef ISC_TRANSLATOR_CONFIG_H
+#define ISC_TRANSLATOR_CONFIG_H 1
+
+#include <yang/translator.h>
+#include <yang/translator_control_socket.h>
+#include <yang/translator_database.h>
+#include <yang/translator_class.h>
+#include <yang/translator_shared_network.h>
+#include <yang/translator_logger.h>
+#include <list>
+
+namespace isc {
+namespace yang {
+
+/// Configuration translation between YANG and JSON
+///
+/// JSON syntax for kea-dhcp6-server is:
+/// @code
+/// "Dhcp6": {
+///     "preferred-lifetime": <preferred lifetime>,
+///     "valid-lifetime": <valid lifetime>,
+///     "renew-timer": <renew timer>,
+///     "rebind-timer": <rebind timer>,
+///     "decline-probation-period": <decline probation period>,
+///     "subnet6": [ <list of subnet6> ],
+///     <shared-networks>,
+///     "interfaces-config": {
+///         "interfaces" [ <list of interface names / specs> ],
+///         ...
+///     },
+///     <databases>,
+///     <mac-sources>,
+///     <relay-supplied-options>,
+///     <host-reservation-identifiers>,
+///     <client-classes>,
+///     <option-def>,
+///     <option-data>,
+///     <hooks-libraries>,
+///     <expired-leases-processing>,
+///     <server-id>,
+///     <dhcp4o6-port>,
+///     <control-socket>,
+///     <dhcp-ddns>,
+///     <user-context>,
+///     <comment>
+/// },
+/// "Logging": <logging>
+/// @endcode
+///
+/// @todo: add kea-dhcp4 syntax and expand logging.
+///
+/// YANG syntax for kea-dhcp6-server:config is:
+/// @code
+/// +--rw preferred-lifetime?            uint32
+/// +--rw valid-lifetime?                uint32
+/// +--rw renew-timer?                   uint32
+/// +--rw rebind-timer?                  uint32
+/// +--rw decline-probation-period?      uint32
+/// +--rw subnet6                        subnet6*
+/// +--rw shared-networks                shared-network*
+/// +--rw interfaces-config
+///    +--rw interfaces*                 string
+///    +--rw re-detect?                  boolean
+///    +--rw user-context?               string
+/// +--rw lease-database!                <database>
+/// +--rw hosts-databases                hosts-database*
+/// +--rw relay-supplied-options*        string
+/// +--rw mac-sources*                   string
+/// +--rw host-reservation-identifiers*  enumeration
+/// +--rw client-classes                 client-class*
+/// +--rw option-def-list                option-def*
+/// +--rw option-data-list               option-data*
+/// +--rw hook-library*
+///    +--rw library                     string
+///    +--rw parameters?                 string
+/// +--rw expired-leases-processing      <expired-leases-processing>
+/// +--rw server-id                      <server-id>
+/// +--rw dhcp4o6-port?                  uint16
+/// +--rw control-socket!                <control-socket>
+/// +--rw dhcp-ddns                      <dhcp-ddns>
+/// +--rw echo-client-id?                boolean
+/// +--rw user-context?                  string
+/// +--rw sanity-checks
+///    +--rw lease-checks?               enumeration
+/// @endcode
+///
+/// YANG syntax for kea-dhcp4-server:config is:
+/// @code
+/// +--rw valid-lifetime?                uint32
+/// +--rw renew-timer?                   uint32
+/// +--rw rebind-timer?                  uint32
+/// +--rw decline-probation-period?      uint32
+/// +--rw subnet4                        subnet4*
+/// +--rw shared-networks                shared-network*
+/// +--rw interfaces-config
+///    +--rw interfaces*                 string
+///    +--rw dhcp-socket-type?           enumeration
+///    +--rw outbound-interface?         enumeration
+///    +--rw re-detect?                  boolean
+///    +--rw user-context?               string
+/// +--rw lease-database!                <database>
+/// +--rw hosts-databases                hosts-database*
+/// +--rw host-reservation-identifiers*  enumeration
+/// +--rw client-classes                 client-class*
+/// +--rw option-def-list                option-def*
+/// +--rw option-data-list               option-data*
+/// +--rw hook-library*
+///    +--rw library                     string
+///    +--rw parameters?                 string
+/// +--rw expired-leases-processing      <expired-leases-processing>
+/// +--rw dhcp4o6-port?                  uint16
+/// +--rw control-socket!                <control-socket>
+/// +--rw dhcp-ddns                      <dhcp-ddns>
+/// +--rw echo-client-id?                boolean
+/// +--rw match-client-id?               boolean
+/// +--rw next-server?                   inet:ipv4-address
+/// +--rw server-hostname?               string
+/// +--rw boot-file-name?                string
+/// +--rw user-context?                  string
+/// +--rw sanity-checks
+///    +--rw lease-checks?               enumeration
+/// @endcode
+///
+/// YANG syntax for kea-*:logging is:
+/// @code
+/// +--rw logging
+///    +--rw loggers
+/// @endcode
+///
+/// @todo add example
+///
+/// Inheritance graph between translators is:
+///
+///        +-----------------------------------------+
+///        |                                         |
+///        +------------------------------+          |
+///        |                              |          |
+///        +----------+-------------------+----------+
+///        |          |                   |          |
+///        |          |         +---------+----------+
+///        |          |         |         |          |
+/// config +- shared  +- subnet +- pool --+- option -+ basic
+///        |  network    (list) |  (list) |  data    |
+///        |  (list)            |         |  (list)  |
+///        |                    |         |          |
+///        |                    +- pd ----+          |
+///        |                    |  pool   |          |
+///        |                    |  (list) |          |
+///        |                    |         |          |          
+///        +--------------------+- host --+          |
+///        |                       (liat) |          |
+///        |                              |          |
+///        |         +--------------------+----------+
+///        |         |                               |
+///        +- class -+- option ----------------------+
+///        |  (list) |  def                          |
+///        |         |  (list)                       |
+///        +---------+                               |
+///        |                                         |
+///        + control --------------------------------+
+///        | socket                                  |
+///        |                                         |
+///        +------------+                            |
+///        |            |                            |
+///        +- database -+- database -----------------+
+///        |  list                                   |
+///        |                                         |
+///        +- logger --------------------------------+
+///           (list)
+///
+///  'XXX (list)' stands for 'XXX list --- XXX' which is a common motif
+///  (only database shows direct dependencies on both the list and the element)
+///
+/// @brief A translator class for converting the config between
+/// YANG and JSON.
+///
+/// Currently supports kea-dhcp[46]-server, kea-logging and partially
+/// ietf-dhcpv6-server.
+class TranslatorConfig : virtual public TranslatorControlSocket,
+    virtual public TranslatorDatabases,
+    virtual public TranslatorClasses,
+    virtual public TranslatorSharedNetworks,
+    virtual public TranslatorLoggers {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param session Sysrepo session.
+    /// @param model Model name.
+    TranslatorConfig(S_Session session, const std::string& model);
+
+    /// @brief Destructor.
+    virtual ~TranslatorConfig();
+
+    /// @brief Get and translate a pool from YANG to JSON.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getConfig();
+
+    /// @brief Translate and set config from JSON to YANG.
+    ///
+    /// Null elem argument removes the config containers.
+    ///
+    /// @param elem The JSON element.
+    void setConfig(isc::data::ConstElementPtr elem);
+
+protected:
+    /// @brief getConfig for ietf-dhcpv6-server.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getConfigIetf6();
+
+    /// @brief delConfig for ietf-dhcpv6-server.
+    void delConfigIetf6();
+
+    /// @brief setConfig for ietf-dhcpv6-server.
+    ///
+    /// @param elem The JSON element.
+    /// @throw BadValue on config without Dhcp6.
+    void setConfigIetf6(isc::data::ConstElementPtr elem);
+
+    /// @brief getConfig for kea-dhcp4-server.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getConfigKea4();
+
+    /// @brief getConfig for kea-dhcp6-server.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getConfigKea6();
+
+    /// @brief getServer common part for kea-dhcp[46]:config.
+    ///
+    /// @param xpath The xpath of the server.
+    /// @return JSON representation of the server.
+    isc::data::ElementPtr getServerKeaDhcpCommon(const std::string& xpath);
+
+    /// @brief getServer for kea-dhcp4-server:config.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getServerKeaDhcp4();
+
+    /// @brief getServer for kea-dhcp6-server:config.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getServerKeaDhcp6();
+
+    /// @brief getServer for kea-*:logging.
+    ///
+    /// @return JSON representation of the config.
+    /// @throw SysrepoError when sysrepo raises an error.
+    isc::data::ElementPtr getServerKeaLogging();
+
+    /// @brief delConfig for kea-dhcp[46]-server.
+    void delConfigKea();
+
+    /// @brief setConfig for kea-dhcp4-server.
+    ///
+    /// @param elem The JSON element.
+    void setConfigKea4(isc::data::ConstElementPtr elem);
+
+    /// @brief setConfig for kea-dhcp6-server.
+    ///
+    /// @param elem The JSON element.
+    void setConfigKea6(isc::data::ConstElementPtr elem);
+
+    /// @brief setServer common part for kea-dhcp[46]:config.
+    ///
+    /// @param xpath The xpath of the server.
+    /// @param elem The JSON element.
+    void setServerKeaDhcpCommon(const std::string& xpath,
+                                isc::data::ConstElementPtr elem);
+
+    /// @brief setServer for kea-dhcp4-server:config.
+    ///
+    /// @param elem The JSON element.
+    void setServerKeaDhcp4(isc::data::ConstElementPtr elem);
+
+    /// @brief setServer for kea-dhcp6-server:config.
+    ///
+    /// @param elem The JSON element.
+    void setServerKeaDhcp6(isc::data::ConstElementPtr elem);
+
+    /// @brief setServer for kea-*:logging.
+    ///
+    /// @param elem The JSON element.
+    void setServerKeaLogging(isc::data::ConstElementPtr elem);
+
+    /// @brief The model.
+    std::string model_;
+};
+
+}; // end of namespace isc::yang
+}; // end of namespace isc
+
+#endif // ISC_TRANSLATOR_CONFIG_H