]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
sysrepo-lua: created abstraction for reading and writing configuration to sysrepo
authorVasek Sraier <git@vakabus.cz>
Sat, 1 Feb 2020 13:45:24 +0000 (14:45 +0100)
committerVasek Sraier <git@vakabus.cz>
Sat, 1 Feb 2020 13:45:24 +0000 (14:45 +0100)
modules/sysrepo-lua/cbindings/sysrepo_clib.c
modules/sysrepo-lua/cbindings/sysrepo_clib.h
modules/sysrepo-lua/debug.lua [new file with mode: 0644]
modules/sysrepo-lua/ffi.lua.in
modules/sysrepo-lua/init.lua
modules/sysrepo-lua/meson.build
modules/sysrepo-lua/model.lua [new file with mode: 0644]

index 22201309879809e478f9e3f93ee7262be26ca2b7..86b49a46495d171cd39056977633aa46f82bbc56 100644 (file)
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 #include <sysrepo.h>
 #include <uv.h>
+#include <libyang/libyang.h>
 
 #include "lib/module.h"
 #include "common/sysrepo_conf.h"
@@ -36,7 +37,9 @@ struct el_subscription_ctx {
 };
 
 /** Callback to Lua used for applying configuration  */
-static set_leaf_conf_t apply_conf_f = NULL;
+static apply_conf_f apply_conf = NULL;
+/** Callback to Lua used for reading configuration */
+static read_conf_f read_conf = NULL;
 static el_subscription_ctx_t *el_subscription_ctx = NULL;
 
 /**
@@ -64,27 +67,20 @@ static int sysrepo_conf_change_callback(sr_session_ctx_t *session,
                // declining the change now
 
                int sr_err = SR_ERR_OK;
-               sr_change_iter_t *it = NULL;
-               sr_change_oper_t oper;
-               sr_val_t *old_value = NULL;
-               sr_val_t *new_value = NULL;
+               struct lyd_node* tree;
 
-               // get all changes
-               sr_err = sr_get_changes_iter(session, XPATH_BASE "//.", &it);
+               // get the whole config tree
+               sr_err = sr_get_data(session, XPATH_BASE, 0, 0, 0, &tree);
                if (sr_err != SR_ERR_OK)
                        goto cleanup;
 
-               while ((sr_get_change_next(session, it, &oper, &old_value,
-                                          &new_value)) == SR_ERR_OK) {
-                       apply_conf_f(new_value);
-               }
+               // apply the configuration
+               apply_conf(tree);
        cleanup:
                if (sr_err != SR_ERR_OK && sr_err != SR_ERR_NOT_FOUND)
                        kr_log_error("Sysrepo module error: %s\n",
                                     sr_strerror(sr_err));
-               sr_free_val(old_value);
-               sr_free_val(new_value);
-               sr_free_change_iter(it);
+               lyd_free_withsiblings(tree); // FIXME sure about this?
        }
        return SR_ERR_OK;
 }
@@ -149,10 +145,10 @@ static void el_subscr_cb(el_subscription_ctx_t *el_subscr, int status)
        sr_process_events(el_subscr->subscription, el_subscr->session, NULL);
 }
 
-int sysrepo_init(set_leaf_conf_t apply_conf_callback)
+int sysrepo_init(apply_conf_f apply_conf_callback)
 {
        // store callback to Lua
-       apply_conf_f = apply_conf_callback;
+       apply_conf = apply_conf_callback;
 
        int sr_err = SR_ERR_OK;
        sr_conn_ctx_t *sr_connection = NULL;
@@ -200,6 +196,33 @@ int sysrepo_deinit()
 {
        el_subscription_free(el_subscription_ctx);
        // remove reference to Lua callback so that it can be free'd safely
-       apply_conf_f = NULL;
+       apply_conf = NULL;
        return kr_ok();
 }
+
+KR_EXPORT struct lyd_node* node_child_first(struct lyd_node* parent) {
+       assert(
+               parent->schema->nodetype == LYS_CONTAINER ||
+               parent->schema->nodetype == LYS_LIST ||
+               parent->schema->nodetype == LYS_CHOICE
+       );
+
+       return parent->child;
+}
+
+KR_EXPORT struct lyd_node* node_child_next(struct lyd_node* prev_child) {
+       return prev_child->next;
+}
+
+KR_EXPORT const char* node_get_name(struct lyd_node* node) {
+       return node->schema->name;
+}
+
+KR_EXPORT const char* node_get_value_str(struct lyd_node* node) {
+       assert(
+               node->schema->nodetype ==  LYS_LEAF ||
+               node->schema->nodetype == LYS_LEAFLIST
+       );
+
+       return ((struct lyd_node_leaf_list*) node)->value_str;
+}
index 9ac10824a1c4b74ef3ba87744db030d0a0e00944..1b2bf00c0f754e54633732c8fc4e2d1625fa7ac9 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "lib/defines.h"
 #include "lib/utils.h"
+#include <libyang/libyang.h>
 
 #include "common/sysrepo_conf.h"
 
@@ -43,7 +44,17 @@ typedef struct el_subscription_ctx el_subscription_ctx_t;
 /** Callback for our sysrepo subscriptions */
 typedef void (*el_subsription_cb)(el_subscription_ctx_t *el_subscr, int status);
 /** Callback to Lua for applying configuration */
-typedef void (*set_leaf_conf_t)(sr_val_t *val);
+typedef void (*apply_conf_f)(struct lyd_node *root);
+typedef void (*read_conf_f)(struct lyd_node* root);
 
-KR_EXPORT int sysrepo_init(set_leaf_conf_t set_leaf_conf_cb);
+KR_EXPORT int sysrepo_init(apply_conf_f set_leaf_conf_cb);
 KR_EXPORT int sysrepo_deinit(void);
+
+/** Given a libyang node, returns it's first child */
+KR_EXPORT struct lyd_node* node_child_first(struct lyd_node* parent);
+/** Given a libyang node, return next sibling or NULL if there isn't any */
+KR_EXPORT struct lyd_node* node_child_next(struct lyd_node* prev_child);
+/** Given a libyang node, return it's name from schema */
+KR_EXPORT const char* node_get_name(struct lyd_node* node);
+/** Given a libyang node, return it's value as a string */
+KR_EXPORT const char* node_get_value_str(struct lyd_node* node);
diff --git a/modules/sysrepo-lua/debug.lua b/modules/sysrepo-lua/debug.lua
new file mode 100644 (file)
index 0000000..ca68a56
--- /dev/null
@@ -0,0 +1,23 @@
+local debug = {}
+
+local DEBUGGING_ENABLED = true
+
+local function pack_table(...)
+    return { n = select("#", ...), ... }
+end
+
+
+function debug.log(msg, ...)
+    if not DEBUGGING_ENABLED then
+        return
+    end
+
+    local args = pack_table(...)
+    for i=1,args.n do
+        msg = string.gsub(msg, "{}", tostring(args[i]), 1)
+    end
+
+    print("[LUA DEBUG] " .. msg)
+end
+
+return debug
index 6fcfc16dc15cdd45a71a07280949da239429fa54..7bdf2d5b6e377eccce17549ac9482d97a8131f91 100644 (file)
@@ -10,93 +10,21 @@ local clib = ffi.load("@modules_dir@/sysrepo-lua/cbindings.so")
 local function initialize_ffi()
     --- Definition of `sr_val_t` copied from sysrepo.h on 2020-01-01.
     ffi.cdef[[
-    /**
-     * @brief Possible types of a data element stored in the sysrepo datastore.
-     */
-     typedef enum sr_type_e {
-        /* special types that does not contain any data */
-        SR_UNKNOWN_T,              /**< Element unknown to sysrepo (unsupported element). */
-
-        SR_LIST_T,                 /**< List instance. ([RFC 7950 sec 7.8](http://tools.ietf.org/html/rfc7950#section-7.8)) */
-        SR_CONTAINER_T,            /**< Non-presence container. ([RFC 7950 sec 7.5](http://tools.ietf.org/html/rfc7950#section-7.5)) */
-        SR_CONTAINER_PRESENCE_T,   /**< Presence container. ([RFC 7950 sec 7.5.1](http://tools.ietf.org/html/rfc7950#section-7.5.1)) */
-        SR_LEAF_EMPTY_T,           /**< A leaf that does not hold any value ([RFC 7950 sec 9.11](http://tools.ietf.org/html/rfc7950#section-9.11)) */
-        SR_NOTIFICATION_T,         /**< Notification instance ([RFC 7095 sec 7.16](https://tools.ietf.org/html/rfc7950#section-7.16)) */
-
-        /* types containing some data */
-        SR_BINARY_T,       /**< Base64-encoded binary data ([RFC 7950 sec 9.8](http://tools.ietf.org/html/rfc7950#section-9.8)) */
-        SR_BITS_T,         /**< A set of bits or flags ([RFC 7950 sec 9.7](http://tools.ietf.org/html/rfc7950#section-9.7)) */
-        SR_BOOL_T,         /**< A boolean value ([RFC 7950 sec 9.5](http://tools.ietf.org/html/rfc7950#section-9.5)) */
-        SR_DECIMAL64_T,    /**< 64-bit signed decimal number ([RFC 7950 sec 9.3](http://tools.ietf.org/html/rfc7950#section-9.3)) */
-        SR_ENUM_T,         /**< A string from enumerated strings list ([RFC 7950 sec 9.6](http://tools.ietf.org/html/rfc7950#section-9.6)) */
-        SR_IDENTITYREF_T,  /**< A reference to an abstract identity ([RFC 7950 sec 9.10](http://tools.ietf.org/html/rfc7950#section-9.10)) */
-        SR_INSTANCEID_T,   /**< References a data tree node ([RFC 7950 sec 9.13](http://tools.ietf.org/html/rfc7950#section-9.13)) */
-        SR_INT8_T,         /**< 8-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_INT16_T,        /**< 16-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_INT32_T,        /**< 32-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_INT64_T,        /**< 64-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_STRING_T,       /**< Human-readable string ([RFC 7950 sec 9.4](http://tools.ietf.org/html/rfc7950#section-9.4)) */
-        SR_UINT8_T,        /**< 8-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_UINT16_T,       /**< 16-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_UINT32_T,       /**< 32-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_UINT64_T,       /**< 64-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        SR_ANYXML_T,       /**< Unknown chunk of XML ([RFC 7950 sec 7.10](https://tools.ietf.org/html/rfc7950#section-7.10)) */
-        SR_ANYDATA_T,      /**< Unknown set of nodes, encoded in XML ([RFC 7950 sec 7.10](https://tools.ietf.org/html/rfc7950#section-7.10)) */
-    } sr_type_t;
-
-    /**
-     * @brief Data of an element (if applicable), properly set according to the type.
-     */
-    typedef union sr_data_u {
-        char *binary_val;       /**< Base64-encoded binary data ([RFC 7950 sec 9.8](http://tools.ietf.org/html/rfc7950#section-9.8)) */
-        char *bits_val;         /**< A set of bits or flags ([RFC 7950 sec 9.7](http://tools.ietf.org/html/rfc7950#section-9.7)) */
-        bool bool_val;          /**< A boolean value ([RFC 7950 sec 9.5](http://tools.ietf.org/html/rfc7950#section-9.5)) */
-        double decimal64_val;   /**< 64-bit signed decimal number ([RFC 7950 sec 9.3](http://tools.ietf.org/html/rfc7950#section-9.3)) */
-        char *enum_val;         /**< A string from enumerated strings list ([RFC 7950 sec 9.6](http://tools.ietf.org/html/rfc7950#section-9.6)) */
-        char *identityref_val;  /**< A reference to an abstract identity ([RFC 7950 sec 9.10](http://tools.ietf.org/html/rfc7950#section-9.10)) */
-        char *instanceid_val;   /**< References a data tree node ([RFC 7950 sec 9.13](http://tools.ietf.org/html/rfc7950#section-9.13)) */
-        int8_t int8_val;        /**< 8-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        int16_t int16_val;      /**< 16-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        int32_t int32_val;      /**< 32-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        int64_t int64_val;      /**< 64-bit signed integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        char *string_val;       /**< Human-readable string ([RFC 7950 sec 9.4](http://tools.ietf.org/html/rfc7950#section-9.4)) */
-        uint8_t uint8_val;      /**< 8-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        uint16_t uint16_val;    /**< 16-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        uint32_t uint32_val;    /**< 32-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        uint64_t uint64_val;    /**< 64-bit unsigned integer ([RFC 7950 sec 9.2](https://tools.ietf.org/html/rfc7950#section-9.2)) */
-        char *anyxml_val;       /**< Unknown chunk of XML ([RFC 7950 sec 7.10](https://tools.ietf.org/html/rfc7950#section-7.10)) */
-        char *anydata_val;      /**< Unknown set of nodes, encoded in XML ([RFC 7950 sec 7.10](https://tools.ietf.org/html/rfc7950#section-7.10)) */
-    } sr_data_t;
-
-    /**
-     * @brief Structure that contains value of an data element stored in the sysrepo datastore.
-     */
-    typedef struct sr_val_s {
-        /** [XPath](@ref paths) (or rather path) identifier of the data element. */
-        char *xpath;
-
-        /** Type of an element. */
-        sr_type_t type;
-
-        /**
-         * Flag for node with default value (applicable only for leaves).
-         * It is set to TRUE only if the value was *implicitly* set by the datastore as per
-         * module schema. Explicitly set/modified data element (through the sysrepo API) always
-         * has this flag unset regardless of the entered value.
-         */
-        bool dflt;
-
-        /** [Origin](@ref oper_ds) of the value. */
-        char *origin;
-
-        /** Data of an element (if applicable), properly set according to the type. */
-        sr_data_t data;
-
-    } sr_val_t;
-
-    typedef void (*set_leaf_conf_t)(sr_val_t *val);
-    int sysrepo_init(set_leaf_conf_t set_leaf_conf_cb);
+
+    typedef void (*apply_conf_f)(struct lyd_node *root);
+    typedef void (*read_conf_f)(struct lyd_node* root);
+    int sysrepo_init(apply_conf_f apply_conf);
     int sysrepo_deinit(void);
+
+    /** Given a libyang node, returns it's first child */
+    struct lyd_node* node_child_first(struct lyd_node* parent);
+    /** Given a libyang node, return next sibling or NULL if there isn't any */
+    struct lyd_node* node_child_next(struct lyd_node* prev_child);
+    /** Given a libyang node, return it's name from schema */
+    const char* node_get_name(struct lyd_node* node);
+    /** Given a libyang node, return it's value as a string */
+    const char* node_get_value_str(struct lyd_node* node);
+
     ]]
 end
 
@@ -111,72 +39,6 @@ end
 
 -- TODO version check so that we can not load new module into an old Knot
 
--------------------------------------------------------------------------------
---                      Data convertsion helpers
--------------------------------------------------------------------------------
-
---- Helper table for converting sr_data_t union type to string based on the provided type
-local _value_to_str_conversion_table = {
-    ["SR_UNKNOWN_T"] = function(_) return "unknown value" end,
-    ["SR_LIST_T"] = function(_) return "no value (list)" end,
-    ["SR_CONTAINER_T"] = function(_) return "no value (container)" end,
-    ["SR_CONTAINER_PRESENCE_T"] = function(_) return "container (container presence)" end,
-    ["SR_LEAF_EMPTY_T"] = function(_) return "empty (empty leaf)" end,
-    ["SR_NOTIFICATION_T"] = function(_) return "no value (notification)" end,
-    ["SR_BINARY_T"] = function(val) return ffi.string(val.binary_val) .. " (binary)" end,
-    ["SR_BITS_T"] = function(_) return "??? (bits)" end,
-    ["SR_BOOL_T"] = function(val) return tostring(val.bool_val) .. " (bool)" end,
-    ["SR_DECIMAL64_T"] = function(val) return tostring(val.decimal64_val) .. "(decimal64)" end,
-    ["SR_ENUM_T"] = function(val) return ffi.string(val.enum_val) .. " (enum)" end,
-    ["SR_IDENTITYREF_T"] = function(val) return ffi.string(val.enum_val) .. " (indentityref)" end,
-    ["SR_INSTANCEID_T"] = function(val) return ffi.string(val.enum_val) .. " (instanceid)" end,
-    ["SR_INT8_T"] = function(val) return tostring(val.int8_val) .. " (int8)" end,
-    ["SR_INT16_T"] = function(val) return tostring(val.int16_val) .. " (int16)" end,
-    ["SR_INT32_T"] = function(val) return tostring(val.int32_val) .. " (int32)" end,
-    ["SR_INT64_T"] = function(val) return tostring(val.int64_val) .. " (int64)" end,
-    ["SR_STRING_T"] = function(val) return ffi.string(val.string_val) .. " (string)" end,
-    ["SR_UINT8_T"] = function(val) return tostring(val.uint8_val) .. " (int8)" end,
-    ["SR_UINT16_T"] = function(val) return tostring(val.uint16_val) .. " (uint16)" end,
-    ["SR_UINT32_T"] = function(val) return tostring(val.uint32_val) .. " (uint32)" end,
-    ["SR_UINT64_T"] = function(val) return tostring(val.uint64_val) .. " (uint64)" end,
-    ["SR_ANYXML_T"] = function(val) return ffi.string(val.anyxml_val) .. " (anyxml)" end,
-    ["SR_ANYDATA_T"] = function(val) return ffi.string(val.anydata_val) .. " (anydata)" end,
-}
-
---- Convert from type sr_type_t into a uppercase string.
--- @param sr_type_t value. In case of wrong type, the function crashes the whole runtime.
--- @return string value of the enum
-local function type_to_str(tp)
-    if (tp == "SR_UNKNOWN_T") then return "SR_UNKNOWN_T"
-    elseif (tp == "SR_LIST_T") then return "SR_LIST_T"
-    elseif (tp == "SR_CONTAINER_T") then return "SR_CONTAINER_T"
-    elseif (tp == "SR_CONTAINER_PRESENCE_T") then return "SR_CONTAINER_PRESENCE_T"
-    elseif (tp == "SR_LEAF_EMPTY_T") then return "SR_LEAF_EMPTY_T"
-    elseif (tp == "SR_NOTIFICATION_T") then return "SR_NOTIFICATION_T"
-    elseif (tp == "SR_BINARY_T") then return "SR_BINARY_T"
-    elseif (tp == "SR_BITS_T") then return "SR_BITS_T"
-    elseif (tp == "SR_BOOL_T") then return "SR_BOOL_T"
-    elseif (tp == "SR_DECIMAL64_T") then return "SR_DECIMAL64_T"
-    elseif (tp == "SR_ENUM_T") then return "SR_ENUM_T"
-    elseif (tp == "SR_IDENTITYREF_T") then return "SR_IDENTITYREF_T"
-    elseif (tp == "SR_INSTANCEID_T") then return "SR_INSTANCEID_T"
-    elseif (tp == "SR_INT8_T") then return "SR_INT8_T"
-    elseif (tp == "SR_INT16_T") then return "SR_INT16_T"
-    elseif (tp == "SR_INT32_T") then return "SR_INT32_T"
-    elseif (tp == "SR_INT64_T") then return "SR_INT64_T"
-    elseif (tp == "SR_STRING_T") then return "SR_STRING_T"
-    elseif (tp == "SR_UINT8_T") then return "SR_UINT8_T"
-    elseif (tp == "SR_UINT16_T") then return "SR_UINT16_T"
-    elseif (tp == "SR_UINT32_T") then return "SR_UINT32_T"
-    elseif (tp == "SR_UINT64_T") then return "SR_UINT64_T"
-    elseif (tp == "SR_ANYXML_T") then return "SR_ANYXML_T"
-    elseif (tp == "SR_ANYDATA_T") then return "SR_ANYDATA_T"
-    else
-        error("unexpected value of sr_type_t enum")
-    end
-end
-
-
 -------------------------------------------------------------------------------
 --                      Callback management
 -------------------------------------------------------------------------------
@@ -205,7 +67,7 @@ end
 local sysrepo_ffi = {}
 
 function sysrepo_ffi.init(apply_conf_func)
-    local cb = create_callback("set_leaf_conf_t", apply_conf_func)
+    local cb = create_callback("apply_conf_f", apply_conf_func)
     local res = clib.sysrepo_init(cb)
     if res ~= 0 then
         error("Initialization failed with error code " .. tostring(res))
@@ -220,26 +82,8 @@ function sysrepo_ffi.deinit()
     end
 end
 
---- Converts from cdata sr_val_t to table (using table is slower, but safer)
-function sysrepo_ffi.sr_val_to_table(val)
-    local tbl = {
-        dflt = val.dflt,
-        xpath = ffi.string(val.xpath),
-        type = type_to_str(val.type),
-        _value = val.data,
-    }
-    setmetatable(tbl, {
-        __tostring = function(slf)
-            return string.format(
-                "{\n\txpath = '%s',\n\ttype = '%s',\n\tdflt = '%s',\n\tdata = '%s'\n}",
-                slf.xpath,
-                slf.type,
-                tostring(slf.dflt),
-                _value_to_str_conversion_table[type_to_str(slf.type)](slf._value)
-            )
-        end
-    })
-    return tbl
+function sysrepo_ffi.get_clib_bindings()
+    return clib
 end
 
 return sysrepo_ffi
index 8ba7daea27c38c1d9acc04b1392125b1775313b0..44270a692646f763de01612e30b617721bee1edb 100644 (file)
@@ -1,12 +1,17 @@
 local sysrepo_ffi = require("kres_modules/sysrepo-lua/ffi")
+ -- following require returns only module constructor, calling it straight away
+local data_model = require("kres_modules/sysrepo-lua/model")(sysrepo_ffi.get_clib_bindings())
 
 local sysrepo = {}
 
-local function apply_configuration(sr_val)
-    local sr_val_table = sysrepo_ffi.sr_val_to_table(sr_val)
+local function apply_configuration(root_node)
+    print("Configuration has changed. Applying the new config!")
 
-    print("Configuration change")
-    print(tostring(sr_val_table))
+    data_model.apply_configuration(root_node)
+end
+
+local function read_configuration()
+    return data_model.serialize_model(nil)
 end
 
 function sysrepo.init()
index 9391753db37b6b5d61ea2248be88b986be7f8bef..05a145510e8abd1a9ed09d69b2be26e854d91ceb 100644 (file)
@@ -12,6 +12,8 @@ ffi = configure_file(
 sysrepo_lua_src = [
   ffi,
   files('init.lua'),
+  files('debug.lua'),
+  files('model.lua'),
 ]
 
 sysrepo_src = files([
@@ -39,7 +41,7 @@ if build_sysrepo
        sysrepo_src,
        dependencies: [
          luajit_inc,
-      libyang,
+    libyang,
          libsysrepo,
        ],
        include_directories: mod_inc_dir,
diff --git a/modules/sysrepo-lua/model.lua b/modules/sysrepo-lua/model.lua
new file mode 100644 (file)
index 0000000..11d6688
--- /dev/null
@@ -0,0 +1,130 @@
+local debug = require("kres_modules/sysrepo-lua/debug")
+local ffi = require("ffi")
+
+local Node = {}
+Node.__index = Node
+
+local _clib = nil
+local function clib()
+    assert(_clib ~= nil)
+    return _clib
+end
+
+--- Tree node for representing a vertex in configuration model tree
+---
+--- Nodes can be read by node:read(data_node) and written by node:write(parent_data_node)
+---
+--- @param name Name of the vertex for constructing XPath
+--- @param read_func Function which takes self and data node from libyang and applies the configuration to the system
+--- @param write_func Function which takes self and data node from libyang and adds a child to it with data from the system
+function Node:create(name, read_func, write_func)
+    assert(type(name) == "string")
+    assert(type(read_func) == 'function')
+    assert(type(write_func) == 'function')
+
+    local handler = {}
+    setmetatable(handler, Node)
+
+    handler.read = read_func
+    handler.write = write_func
+    handler.name = name
+
+    return handler
+end
+
+local function DummyLeafNode(name, ignore_value)
+    local function dummy_read(self, node)
+        if ignore_value then
+            debug.log(
+                "Dummy read on node named \"{}\", actual name \"{}\"",
+                self.name,
+                ffi.string(clib().node_get_name(node))
+            )
+        else
+            debug.log(
+                "Dummy read on node named \"{}\", actual name \"{}\". Contains value (as a string) \"{}\"",
+                self.name,
+                ffi.string(clib().node_get_name(node)),
+                ffi.string(clib().node_get_value_str(node))
+            )
+        end
+    end
+
+    local function dummy_write(self, node)
+        debug.log("dummy write on node named {}", self.name)
+    end
+
+    return Node:create(name, dummy_read, dummy_write)
+end
+
+local function ContainerNode(name, container_model)
+    -- optimize child lookup by name with table
+    local child_lookup_table = {}
+    for _,v in ipairs(container_model) do
+        child_lookup_table[v.name] = v
+    end
+
+    --- Node's read function
+    local function handle_cont_read(self, node)
+        local node_name = ffi.string(clib().node_get_name(node))
+        debug.log("Attempting to read container \"{}\", it's actual name is \"{}\"", self.name, node_name)
+        assert(node_name == self.name)
+
+        local child = clib().node_child_first(node)
+        while child ~= nil do
+            local nm = ffi.string(clib().node_get_name(child))
+            child_lookup_table[nm]:read(child)
+            child = clib().node_child_next(child)
+        end
+    end
+
+    --- Node's write function
+    local function handle_cont_write(self, parent_node)
+        local node = nil -- TODO get current node from parent_node
+
+        for _,v in ipairs(container_model) do
+            v:write(node)
+        end
+    end
+
+    return Node:create(name, handle_cont_read, handle_cont_write)
+end
+
+
+--- Configuration schema reprezentation
+local model = 
+    ContainerNode("dns-resolver", {
+        ContainerNode("cache", {
+            DummyLeafNode("current-size"),
+            DummyLeafNode("max-size"),
+            DummyLeafNode("max-ttl"),
+            DummyLeafNode("min-ttl"),
+            DummyLeafNode("prefill"),
+        }),
+        DummyLeafNode("debugging", true),
+        DummyLeafNode("dns64", true),
+        DummyLeafNode("dnssec", true),
+        DummyLeafNode("garbage-collector", true),
+        DummyLeafNode("logging", true),
+        DummyLeafNode("network", true),
+        DummyLeafNode("resolver", true),
+        DummyLeafNode("server", true),
+    })
+
+
+--- Module constructor
+return function(clib_binding)
+    _clib = clib_binding
+
+    local module = {}
+    function module.serialize_configuration(root_node)
+        model:write(root_node)
+    end
+
+    function module.apply_configuration(root_node)
+        model:read(root_node)
+    end
+
+    return module
+end
+