From: Vasek Sraier Date: Thu, 5 Mar 2020 15:04:36 +0000 (+0100) Subject: sysrepo-lua: initial work on reporting to operational datastore X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=df5308aff71f34098faced446c01c0c03e43cf8d;p=thirdparty%2Fknot-resolver.git sysrepo-lua: initial work on reporting to operational datastore --- diff --git a/modules/sysrepo-lua/cbindings/sysrepo_clib.c b/modules/sysrepo-lua/cbindings/sysrepo_clib.c index 86b49a464..2adf8dd13 100644 --- a/modules/sysrepo-lua/cbindings/sysrepo_clib.c +++ b/modules/sysrepo-lua/cbindings/sysrepo_clib.c @@ -41,6 +41,7 @@ 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; +static const struct lys_node* lys_root = NULL; /** * Change callback getting called by sysrepo. Iterates over changed options and passes @@ -85,6 +86,18 @@ static int sysrepo_conf_change_callback(sr_session_ctx_t *session, return SR_ERR_OK; } +static int sysrepo_conf_read_callback(sr_session_ctx_t *session, const char *module_name, const char *path, const char *request_xpath, uint32_t request_id, struct lyd_node **parent, void *private_data) { + // *parent is expected to be NULL, because we are subscribed only on the top-level node + assert(*parent == NULL); + // so we can safely ignore it and overwrite it with our new tree + struct lyd_node *res = read_conf(); + if (res == NULL) + return SR_ERR_CALLBACK_FAILED; + + *parent = res; + return SR_ERR_OK; +} + void el_subscr_finish_closing(uv_handle_t *handle) { el_subscription_ctx_t *el_subscr = handle->data; @@ -145,10 +158,11 @@ 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(apply_conf_f apply_conf_callback) +int sysrepo_init(apply_conf_f apply_conf_callback, read_conf_f read_conf_callback) { // store callback to Lua apply_conf = apply_conf_callback; + read_conf = read_conf_callback; int sr_err = SR_ERR_OK; sr_conn_ctx_t *sr_connection = NULL; @@ -167,7 +181,12 @@ int sysrepo_init(apply_conf_f apply_conf_callback) if (sr_err != SR_ERR_OK) goto cleanup; - /* register sysrepo subscriptions and callbacks + /* obtain schema root node, will be used from within Lua */ + const struct ly_ctx* ly_context = sr_get_context(sr_connection); + lys_root = ly_ctx_get_node(ly_context, NULL, XPATH_BASE, 0); + assert(lys_root != NULL); + + /* register sysrepo subscription and callback SR_SUBSCR_NO_THREAD - don't create a thread for handling them SR_SUBSCR_ENABLED - send us current configuration in a callback just after subscribing */ @@ -178,6 +197,14 @@ int sysrepo_init(apply_conf_f apply_conf_callback) if (sr_err != SR_ERR_OK) goto cleanup; + sr_err = sr_oper_get_items_subscribe( + sr_session, YM_COMMON, XPATH_BASE, sysrepo_conf_read_callback, + NULL, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_NO_THREAD, + &sr_subscription + ); + if (sr_err != SR_ERR_OK) + goto cleanup; + /* add subscriptions to kres event loop */ el_subscription_ctx = el_subscription_new(sr_subscription, el_subscr_cb); @@ -195,8 +222,11 @@ cleanup: int sysrepo_deinit() { el_subscription_free(el_subscription_ctx); + // remove reference to Lua callback so that it can be free'd safely apply_conf = NULL; + read_conf = NULL; + return kr_ok(); } @@ -226,3 +256,36 @@ KR_EXPORT const char* node_get_value_str(struct lyd_node* node) { return ((struct lyd_node_leaf_list*) node)->value_str; } + +KR_EXPORT struct lyd_node* node_new_leaf(struct lyd_node* parent, const struct lys_module* module, const char* name, const char* value) { + return lyd_new_leaf(parent, module, name, value); +} +KR_EXPORT struct lyd_node* node_new_container(struct lyd_node* parent, const struct lys_module* module, const char* name) { + return lyd_new(parent, module, name); +} +KR_EXPORT const struct lys_module* schema_get_module(const struct lys_node* schema) { + return schema->module; +} + +KR_EXPORT const struct lys_node* schema_child_first(const struct lys_node* parent) { + assert( + parent->nodetype == LYS_CONTAINER || + parent->nodetype == LYS_LIST || + parent->nodetype == LYS_CHOICE + ); + + return parent->child; +} + +KR_EXPORT const struct lys_node* schema_child_next(const struct lys_node* prev_child) { + return prev_child->next; +} + +KR_EXPORT const char* schema_get_name(const struct lys_node* node) { + return node->name; +} + +KR_EXPORT const struct lys_node* schema_root() { + assert(lys_root != NULL); + return lys_root; +} diff --git a/modules/sysrepo-lua/cbindings/sysrepo_clib.h b/modules/sysrepo-lua/cbindings/sysrepo_clib.h index 1b2bf00c0..634481ed5 100644 --- a/modules/sysrepo-lua/cbindings/sysrepo_clib.h +++ b/modules/sysrepo-lua/cbindings/sysrepo_clib.h @@ -45,9 +45,9 @@ typedef struct el_subscription_ctx el_subscription_ctx_t; typedef void (*el_subsription_cb)(el_subscription_ctx_t *el_subscr, int status); /** Callback to Lua for applying configuration */ typedef void (*apply_conf_f)(struct lyd_node *root); -typedef void (*read_conf_f)(struct lyd_node* root); +typedef struct lyd_node* (*read_conf_f)(); -KR_EXPORT int sysrepo_init(apply_conf_f set_leaf_conf_cb); +KR_EXPORT int sysrepo_init(apply_conf_f apply_conf_callback, read_conf_f read_conf_callback); KR_EXPORT int sysrepo_deinit(void); /** Given a libyang node, returns it's first child */ @@ -58,3 +58,17 @@ KR_EXPORT struct lyd_node* node_child_next(struct lyd_node* prev_child); 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); +/** Create a new libyang leaf node */ +KR_EXPORT struct lyd_node* node_new_leaf(struct lyd_node* parent, const struct lys_module* module, const char* name, const char* value); +/** Create a new libyang container node */ +KR_EXPORT struct lyd_node* node_new_container(struct lyd_node* parent, const struct lys_module* module, const char* name); +/** Returns module given a schema node */ +KR_EXPORT const struct lys_module* schema_get_module(const struct lys_node* schema); +/** Given a libyang schema node, returns it's first child */ +KR_EXPORT const struct lys_node* schema_child_first(const struct lys_node* parent); +/** Given a libyang schema node, return next sibling or NULL if there isn't any */ +KR_EXPORT const struct lys_node* schema_child_next(const struct lys_node* prev_child); +/** Given a libyang schema node, return it's name */ +KR_EXPORT const char* schema_get_name(const struct lys_node* node); +/** Get schema root */ +KR_EXPORT const struct lys_node* schema_root(); diff --git a/modules/sysrepo-lua/ffi.lua.in b/modules/sysrepo-lua/ffi.lua.in index 7bdf2d5b6..146444b6f 100644 --- a/modules/sysrepo-lua/ffi.lua.in +++ b/modules/sysrepo-lua/ffi.lua.in @@ -11,20 +11,38 @@ local function initialize_ffi() --- Definition of `sr_val_t` copied from sysrepo.h on 2020-01-01. ffi.cdef[[ - 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); - + 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 (*apply_conf_f)(struct lyd_node *root); + typedef struct lyd_node* (*read_conf_f)(); + + int sysrepo_init(apply_conf_f apply_conf_callback, read_conf_f read_conf_callback); + 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); + /** Create a new libyang leaf node */ + struct lyd_node* node_new_leaf(struct lyd_node* parent, const struct lys_module* module, const char* name, const char* value); + /** Create a new libyang container node */ + struct lyd_node* node_new_container(struct lyd_node* parent, const struct lys_module* module, const char* name); + /** Returns module given a schema node */ + const struct lys_module* schema_get_module(const struct lys_node* schema); + /** Given a libyang schema node, returns it's first child */ + const struct lys_node* schema_child_first(const struct lys_node* parent); + /** Given a libyang schema node, return next sibling or NULL if there isn't any */ + const struct lys_node* schema_child_next(const struct lys_node* prev_child); + /** Given a libyang schema node, return it's name */ + const char* schema_get_name(const struct lys_node* node); + /** Get schema root */ + const struct lys_node* schema_root(); ]] end @@ -66,9 +84,10 @@ end local sysrepo_ffi = {} -function sysrepo_ffi.init(apply_conf_func) - local cb = create_callback("apply_conf_f", apply_conf_func) - local res = clib.sysrepo_init(cb) +function sysrepo_ffi.init(apply_conf_func, read_conf_func) + local apply = create_callback("apply_conf_f", apply_conf_func) + local read = create_callback("read_conf_f", read_conf_func) + local res = clib.sysrepo_init(apply, read) if res ~= 0 then error("Initialization failed with error code " .. tostring(res)) end diff --git a/modules/sysrepo-lua/init.lua b/modules/sysrepo-lua/init.lua index 44270a692..bb29ef3b5 100644 --- a/modules/sysrepo-lua/init.lua +++ b/modules/sysrepo-lua/init.lua @@ -11,11 +11,13 @@ local function apply_configuration(root_node) end local function read_configuration() - return data_model.serialize_model(nil) + print("Current configuration requested") + + return data_model.serialize_configuration(nil) end function sysrepo.init() - sysrepo_ffi.init(apply_configuration) + sysrepo_ffi.init(apply_configuration, read_configuration) end function sysrepo.deinit() diff --git a/modules/sysrepo-lua/model.lua b/modules/sysrepo-lua/model.lua index 11d6688b5..479218931 100644 --- a/modules/sysrepo-lua/model.lua +++ b/modules/sysrepo-lua/model.lua @@ -15,19 +15,32 @@ end --- 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) +--- @param apply_func Function which takes self and data node from libyang and applies the configuration to the system +--- @param read_func Function which takes self and data node from libyang and adds a child to it with data from +--- the system. Returns a node it added. +function Node:create(name, apply_func, read_func, initialize_schema_func) assert(type(name) == "string") + assert(type(apply_func) == 'function') assert(type(read_func) == 'function') - assert(type(write_func) == 'function') + assert(initialize_schema_func == nil or type(initialize_schema_func) == 'function') local handler = {} setmetatable(handler, Node) - handler.read = read_func - handler.write = write_func + handler.apply = apply_func + handler.serialize = read_func handler.name = name + handler.module = nil -- must be filled in later by initialize_schema method + + -- default implementation + local function schema_init(self, lys_node) + assert(ffi.string(clib().schema_get_name(lys_node)) == self.name) + self.module = clib().schema_get_module(lys_node) + end + if initialize_schema_func == nil then + initialize_schema_func = schema_init + end + handler.initialize_schema = initialize_schema_func return handler end @@ -52,9 +65,10 @@ local function DummyLeafNode(name, ignore_value) local function dummy_write(self, node) debug.log("dummy write on node named {}", self.name) + return nil end - return Node:create(name, dummy_read, dummy_write) + return Node:create(name, dummy_read, dummy_write, nil) end local function ContainerNode(name, container_model) @@ -64,7 +78,7 @@ local function ContainerNode(name, container_model) child_lookup_table[v.name] = v end - --- Node's read function + --- Node's apply 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) @@ -73,21 +87,55 @@ local function ContainerNode(name, container_model) 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_lookup_table[nm]:apply(child) child = clib().node_child_next(child) end end - --- Node's write function + --- Node's serialize function local function handle_cont_write(self, parent_node) - local node = nil -- TODO get current node from parent_node + local node = clib().node_new_container(parent_node, self.module, self.name) + + for _,v in ipairs(container_model) do + _ = v:serialize(node) + end + + return node + end + + local function schema_init(self, lys_node) + assert(ffi.string(clib().schema_get_name(lys_node)) == self.name) + self.module = clib().schema_get_module(lys_node) + + local lookup = {} + local child = clib().schema_child_first(lys_node) + while child ~= nil do + local nm = ffi.string(clib().schema_get_name(child)) + lookup[nm] = child + child = clib().schema_child_next(child) + end + -- apply to all children for _,v in ipairs(container_model) do - v:write(node) + v:initialize_schema(lookup[v.name]) end end - return Node:create(name, handle_cont_read, handle_cont_write) + return Node:create(name, handle_cont_read, handle_cont_write, schema_init) +end + +local function StateNode(name, get_val) + --- Node's apply function + local function handle_apply(self, node) + -- do nothing, it's only a state node + end + + --- Node's serialize function + local function handle_serialize(self, parent_node) + return clib().node_new_leaf(parent_node, self.module, self.name, tostring(get_val())) + end + + return Node:create(name, handle_apply, handle_serialize, nil) end @@ -95,7 +143,7 @@ end local model = ContainerNode("dns-resolver", { ContainerNode("cache", { - DummyLeafNode("current-size"), + StateNode("current-size", function() return cache.current_size end), DummyLeafNode("max-size"), DummyLeafNode("max-ttl"), DummyLeafNode("min-ttl"), @@ -111,18 +159,27 @@ local model = DummyLeafNode("server", true), }) - --- Module constructor return function(clib_binding) _clib = clib_binding + local initialized_schema = false + local function init_schema() + if not initialized_schema then + model:initialize_schema(clib().schema_root()) + initialized_schema = true + end + end + local module = {} function module.serialize_configuration(root_node) - model:write(root_node) + init_schema() + model:serialize(root_node) end function module.apply_configuration(root_node) - model:read(root_node) + init_schema() + model:apply(root_node) end return module