本章节主要是通过分析frr sysrepo纳管实现来讲讲frr-7.5是如何实现对sysrepo的支持。以isis协议纳管实现为例。了解本章节的内容需要先了解前面章节的内容。本章节的内容不会过多重复前面的内容。通过前一章节的内容,首先是要确保frr-isisd.yang能在sysrepo中成功加载,这是前提,需要先确保;其次,启动isisd进程。
1 isisd进程的启动分析
Isisd集成sysrepo的启动方法如下:
/usr/lib/frr/isisd -d -M sysrepo:/usr/lib64/frr/modules/ --log-level debugging
【说明1】:调试时,可以增加--log-level选项,便于调试
【说明2】:sysrepo属于frr的一个模块,要加载模块时一定要带-M [module],
否则模块不会启动,如果不带模块的路径,使用的模块路径是编译时指定的默认路径。
isisd进程启动的入口位于{$frr}\isisd\isis_main.c的main函数中,main()函数的frr主线程初始化函数frr_init()。但是在了解frr_init()之前,需要将frr中涉及到di的来源先搞清楚,static struct frr_daemon_info *di = NULL; di是一个指向frr_daemon_info结构的static指 针,在frr_preinit(struct frr_daemon_info ,*daemon,...)函数有这样一行代码 di = daemon; daemon实际是指向所在isisd_di地址空间,所以,di最终指向的是isisd_di所指向的空间,所以看看isisd_di是如实实现完成初始化的:
static struct frr_daemon_info isisd_di; //全局isisd_di结构定义
FRR_DAEMON_INFO(isisd, ISIS, .vty_port = ISISD_VTY_PORT,
.proghelp = "Implementation of the IS-IS routing protocol.",
.copyright =
"Copyright (c) 2001-2002 Sampo Saaristo,"
" Ofer Wald and Hannes Gredler",
.signals = isisd_signals,
.n_signals = array_size(isisd_signals),
.privs = &isisd_privs, .yang_modules = isisd_yang_modules,
.n_yang_modules = array_size(isisd_yang_modules), )
#define FRR_DAEMON_INFO(execname, constname, ...) \
static struct frr_daemon_info execname##_di = {.name = #execname, \
.logname = #constname, \
.module = THIS_MODULE, \
__VA_ARGS__}; \
FRR_COREMOD_SETUP(.name = #execname, \
.description = #execname " daemon", \
.version = FRR_VERSION, )
再看看frr_daemon_info数据结构的定义,
struct frr_daemon_info {
unsigned flags;
const char *progname;
const char *name;
const char *logname;
unsigned short instance;
struct frrmod_runtime *module;
char *vty_addr;
int vty_port;
char *vty_sock_path;
bool dryrun;
bool daemon_mode;
bool terminal;
enum frr_cli_mode cli_mode;
struct thread *read_in;
const char *config_file;
const char *backup_config_file;
const char *pid_file;
#ifdef HAVE_SQLITE3
const char *db_file;
#endif
const char *vty_path;
const char *module_path;
const char *pathspace;
bool zpathspace;
const char *early_logging;
const char *early_loglevel;
const char *proghelp;
void (*printhelp)(FILE *target);
const char *copyright;
char startinfo[128];
struct quagga_signal_t *signals;
size_t n_signals;
struct zebra_privs_t *privs;
const struct frr_yang_module_info *const *yang_modules;
size_t n_yang_modules;
bool log_always;
};
所以,isisd_di结构的初始是通过宏FRR_DAEMON_INFO完成的,
FRR_DAEMON_INFO()中的每项的定义,各项都封装成可变__VA_ARGS__,
保存到frr_daemon_info的对应的各项参数中,
例如.yang_modules = isisd_yang_modules,
就是保存到frr_daemon_info->yang_modules。
通过GDB isisd进程初始化时,打印isisd_di结构就明显可以看出,简单贴一个调试的方法:
gdb isisd
(gdb) set args -d -M sysrepo:/usr/lib64/frr/modules/ --log-level debugging
(gdb) b frr_preinit
(gdb) b frr_init
(gdb) run
执行完frr_preinit后,我们再来看来isisd_di的变化,基本没有变化,这里因为都是使用初始配置,才基本没有变化,如果指定到相应的pid,配置保存文件,也就会有相应的变化,这个不多说。
通过上面的过程的di是怎么来的,在frr_init()中还有一个很重要的概念,module_path,是指sysrepo.so模块路径所在路径,请看调试信息
这个路径是由编译器在编译时指定MODULE_PATH。
而我们在命令行中提供的路径是指定yang文件所在的路径,这个路径是在命令行参数解析时,保存在全局变量中
static struct option_chain *modules = NULL, **modnext = &modules;
通过上面的分析,将模块加载的需要的资源已经详细指出,我们再来看看frr_init()它的实现如下,已经将不相关的删除,只留与module相关的代码,有兴趣的可以对照源码一起看:
struct thread_master *frr_init(void)
{
struct option_chain *oc;
struct frrmod_runtime *module; //正在启动运行的模块
char moderr[256];
//char p_instance[16] = "", p_pathspace[256] = "";
const char *dir;
dir = di->module_path ? di->module_path : frr_moduledir;
/*这段不相关,不在这细说*/
//di->module是指要加载运行的sysrep.so模块
//frrmod_init(),是使用modinfo信息对全局就是的初始化
frrmod_init(di->module);
while (modules) {
//modules是命令行解析所记录的:sysrepo:/usr/lib64/frr/modules/
modules = (oc = modules)->next;
/* oc->arg,sysrepo:/usr/lib64/frr/modules
* /usr/lib64/frr/modules
* 通过dlopen完成syrepo.so的加载
*/
module = frrmod_load(oc->arg, dir, moderr, sizeof(moderr));
if (!module) {
fprintf(stderr, "%s\n", moderr);
exit(1);
}
XFREE(MTYPE_TMP, oc);
}
/*这段不相关,不在这细说*/
yang_init(); //为作用于全部容器的yang上下文的初始化,影响全部的yangy文件。
//debug_init_cli();
/* di->yang_modules是指向isisd_yang_modules[]
* di->n_yang_modules yang的数量,isis就与3个yang相关。
* nb_init(),北向初始化,sysrepo是在frr的上层,与sysrepo间的通道也是北向接口
*/
nb_init(master, di->yang_modules, di->n_yang_modules);
if (nb_db_init() != NB_OK)
flog_warn(EC_LIB_NB_DATABASE,
"%s: failed to initialize northbound database",
__func__);
return master;
}
2 北向接口初始化(nb_init)分析
在分析nb_init()之前,需要讲讲引入的一个非常重要的数据结构红黑树(rb_tree)
红黑树是节点颜色作为额外属性的二叉搜索树,它满足于如下一系列条件:
1)、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
2)、根节点是黑色
3)、所有叶子都是黑色
4)、每个红色节点(除了根节点)都有一个黑色的父节点
红黑树每个操作的时间复杂度不超过O(log n),最大深度2log(n+1)
#define RB_BLACK 0
#define RB_RED 1
struct rbt_tree {
struct rb_entry *rbt_root;
};
struct rb_entry {
struct rb_entry *rbt_parent;
struct rb_entry *rbt_left;
struct rb_entry *rbt_right;
unsigned int rbt_color;
};
//rbtree type定义
struct rb_type {
int (*t_compare)(const void *, const void *);
void (*t_augment)(void *);
unsigned int t_offset; /* offset of rb_entry in type */
};
其它的定义可以参考openbsd-tree.h
而frr中又是如何使用rbtree结构的
首先:
/*构建一颗rb_tree的头结点,root节点*/
RB_HEAD(_name, _type)
/*定义一颗指定类型的RB树的全部操作,该RB树的操作包括rb_init,rb_insert,rb_remove等等*/
#define RB_PROTOTYPE(_name, _type, _field, _cmp)
/*构建指定type的rb_type的比较函数*/
#define RB_GENERATE(_name, _type, _field, _cmp) \
RB_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL)
/*初始化*/
#define RB_INITIALIZER(_head) { { NULL } }
所以,FRR的北侧用到了很多RB操作了,配置的增删改查,都是通过rb-tree实现的。
其次,实际看一下yang模块是如何使用rb-tree的。
/*yang_module的数据结构定义*/
struct yang_module {
RB_ENTRY(yang_module) entry;
const char *name;
const struct lys_module *info;
#ifdef HAVE_CONFD
int confd_hash;
#endif
#ifdef HAVE_SYSREPO
sr_subscription_ctx_t *sr_subscription;
#endif
};
1、
/*构建头节点*/
RB_HEAD(yang_modules, yang_module);
扩展出来的样式为:
struct yang_modules {
struct rbt_tree rbh_root;
}
2、yang_module rb-trees基本操作库函数的构建
RB_PROTOTYPE(yang_modules, yang_module, entry, yang_module_compare);
3、yang_modules的内部entry的比较操作
RB_GENERATE(yang_modules, yang_module, entry, yang_module_compare)
4、定义一个静态的yang_modules结构,用于保存记录
struct yang_modules yang_modules = RB_INITIALIZER(&yang_modules);
5、按实际需求将数据插入到yang_modules的rb_tree中
RB_INSERT(yang_modules, &yang_modules, module);
通过以上,至少对rb的使用有一个简单的理解,再来看看rb_init是操作的
1、yang模块的加载
frr_init()为nb_init()函数传入了yang_module_info结构的实际数据如下:
static const struct frr_yang_module_info *const isisd_yang_modules[] = {
&frr_interface_info,
&frr_isisd_info,
&frr_route_map_info,
};
/*调用libyang提供的解析函数ly_ctx_load_module(),
*将yang文件的内容解析到数据结构struct lys_module,
*并将解析出的module_info插入本地的yang_modules rbtree中。
*/
struct yang_module *yang_module_load(const char *module_name)
{
struct yang_module *module;
const struct lys_module *module_info;
module_info = ly_ctx_load_module(ly_native_ctx, module_name, NULL);
/*错误检查不看,省点空间*/
module = XCALLOC(MTYPE_YANG_MODULE, sizeof(*module));
module->name = module_name;
module->info = module_info;
//模块插入
if (RB_INSERT(yang_modules, &yang_modules, module) != NULL) {
/*错误检查不看,省点空间*/
}
return module;
}
2、frr nb_nodes的创建,YANG文件通过ly_ctx_load_module()的解析
已经将数据保存到于在yang_modules rb tress中,
nb_nodes_create()完成将libyang所支持snode与本地的nb_node建立连接
void nb_nodes_create(void)
{
/*iterate是遍历,是要完成已经解析出的全部YANG模块的全部节点*/
yang_snodes_iterate_all(nb_node_new_cb, 0, NULL);
}
int yang_snodes_iterate_all(yang_iterate_cb cb, uint16_t flags, void *arg)
{
struct yang_module *module;
int ret = YANG_ITER_CONTINUE;
//yang_modules,也就是在解析时,已经完成本地创建的,
//并且所保存的module_info就是YANG的全部数据
RB_FOREACH (module, yang_modules, &yang_modules) {
ret = yang_snodes_iterate_module(module->info, cb, flags, arg);
if (ret == YANG_ITER_STOP)
return ret;
}
return ret;
}
int yang_snodes_iterate_module(const struct lys_module *module,
yang_iterate_cb cb, uint16_t flags, void *arg)
{
struct lys_node *snode;
int ret = YANG_ITER_CONTINUE;
/从libyang tree型数据开始遍历,snode,对应info中所保存的data,
LY_TREE_FOR (module->data, snode) {
/*snodes的遍历是根据不同的节点类型做不同处理,就不一一说,可以看源码*/
ret = yang_snodes_iterate_subtree(snode, cb, flags, arg);
if (ret == YANG_ITER_STOP)
return ret;
}
....
return ret;
}
在snode遍历时,按节点类型解析后,将执行回调实现nb_node的创建;
static int nb_node_new_cb(const struct lys_node *snode, void *arg)
{
struct nb_node *nb_node;
struct lys_node *sparent, *sparent_list;
/*分配nb_noded的内存和相应的初始化*/
nb_node = XCALLOC(MTYPE_NB_NODE, sizeof(*nb_node));
yang_snode_get_path(snode, YANG_PATH_DATA, nb_node->xpath,
sizeof(nb_node->xpath));
nb_node->priority = NB_DFLT_PRIORITY;
sparent = yang_snode_real_parent(snode);
if (sparent)
nb_node->parent = sparent->priv;
sparent_list = yang_snode_parent_list(snode);
if (sparent_list)
nb_node->parent_list = sparent_list->priv;
/* Set flags. */
/*下面非常重要,如果就CONTAINER节点和LIST型节点,需要递归遍历该节点下的全部子节点*/
if (CHECK_FLAG(snode->nodetype, LYS_CONTAINER | LYS_LIST)) {
bool config_only = true;
yang_snodes_iterate_subtree(snode, nb_node_check_config_only,
YANG_ITER_ALLOW_AUGMENTATIONS,
&config_only);
if (config_only)
SET_FLAG(nb_node->flags, F_NB_NODE_CONFIG_ONLY);
}
if (CHECK_FLAG(snode->nodetype, LYS_LIST)) {
struct lys_node_list *slist;
slist = (struct lys_node_list *)snode;
if (slist->keys_size == 0)
SET_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST);
}
/*
* Link the northbound node and the libyang schema node with one
* another.
*/
/*在snode与nb_node建立连接*/
nb_node->snode = snode;
lys_set_private(snode, nb_node);
return YANG_ITER_CONTINUE;
}
3、加载在yang_module_info中定义的全部回调,
先看看frr_yang_module_info就如何定义的,其定义如下:
frr_struct frr_yang_module_info {
const char *name; /* YANG module name. */
/* Northbound callbacks. */
const struct {
const char *xpath; /* Data path of this YANG node. */
struct nb_callbacks cbs; /* Callbacks implemented for this node. */
uint32_t priority; /* Priority - lower priorities are processed first. */
} nodes[];
};
const struct frr_yang_module_info frr_isisd_info = {
.name = "frr-isisd", //模块名
.nodes = {
{
//就是YANG文件实际对应的路径,可以对照frr_isis.yang文件理解
.xpath = "/frr-isisd:isis/instance",
.cbs = { //创建的回调函数
.cli_show = cli_show_router_isis,
.create = isis_instance_create,
.destroy = isis_instance_destroy,
},
.priority = NB_DFLT_PRIORITY - 1,
},
.....
}
/*将预定义的CB挂到指定的nb_node中*/
static void nb_load_callbacks(const struct frr_yang_module_info *module)
{
for (size_t i = 0; module->nodes[i].xpath; i++) {
struct nb_node *nb_node;
uint32_t priority;
/*nb_node_new_cb()的最后完将新建的nb_node挂到snode->priv结构中,
* nb_node_find()是要通过module->nodes[i].xpath定义的path,找到对应的nb_node*/
nb_node = nb_node_find(module->nodes[i].xpath);
if (!nb_node) {
continue;
}
/*找到nb_node后,将预定义的cbs, priority挂载到nb_node,
*需要注意的是,nb_node是保存在 snode->priv的
*/
nb_node->cbs = module->nodes[i].cbs;
priority = module->nodes[i].priority;
if (priority != 0)
nb_node->priority = priority;
}
}
4、用户定义CB的有效性检验,yang_snodes_iterate_all就不在看吧,主要看它的回调实现
static int nb_node_validate(const struct lys_node *snode, void *arg)
{
/*nb_load_callbacks()函数已经实现将用户定义的回调都保存snode->priv中*/
struct nb_node *nb_node = snode->priv;
unsigned int *errors = arg;
/* Validate callbacks and priority. */
*errors += nb_node_validate_cbs(nb_node);
*errors += nb_node_validate_priority(nb_node);
return YANG_ITER_CONTINUE;
}
在看用户定义回调的检验之前,这里就是先大致了解YANG文件,和用户根据yang文件定义的CB间的关系。
module frr-isisd {
yang-version 1.1;
namespace "http://frrouting.org/yang/isisd";
prefix frr-isisd;
<......>
container isis {
description
"Configuration of the IS-IS routing daemon.";
list instance {
key "area-tag";
description
"IS-IS routing instance.";
leaf area-tag {
type string;
description
"Area-tag associated to this routing instance.";
}
<.......>
}
}
}
通过上面的分析,已经它知道,通过libyang提供的的库函数,YANG文件的数据解析并保存与snode中
const struct frr_yang_module_info frr_isisd_info = {
.name = "frr-isisd",
.nodes = {
{
.xpath = "/frr-isisd:isis/instance",
.cbs = {
.cli_show = cli_show_router_isis,
.create = isis_instance_create,
.destroy = isis_instance_destroy,
},
.priority = NB_DFLT_PRIORITY - 1,
},
<..........>
}
通过上面的分析,已经将用户自定义的yang_module_info,
YANG文件映射snode中,用户定义模块CB保存到snode->priv中,也是nb_node中。
bool nb_operation_is_valid(enum nb_operation operation,
const struct lys_node *snode)
{
//看到这,就能感觉到在nb_node_new_cb中将nb_node与snode建立连接的妙处,这是一种双向通道
struct nb_node *nb_node = snode->priv;
struct lys_node_container *scontainer;
struct lys_node_leaf *sleaf;
switch (operation) {
case NB_OP_CREATE:
if (!CHECK_FLAG(snode->flags, LYS_CONFIG_W))
return false;
switch (snode->nodetype) {
case LYS_LEAF:
sleaf = (struct lys_node_leaf *)snode;
if (sleaf->type.base != LY_TYPE_EMPTY)
return false;
break;
case LYS_CONTAINER:
scontainer = (struct lys_node_container *)snode;
if (!scontainer->presence)
return false;
break;
case LYS_LIST:
case LYS_LEAFLIST:
break;
default:
return false;
}
return true;
<.......>
}
5、最后在内存中创建一份空的配置,用于后续直接在内存中增,删,改,查配置。
running_config = nb_config_new(NULL);
running_config_entries = hash_create(running_config_entry_key_make,
running_config_entry_cmp,
"Running Configuration Entries");
通过nb_init(),完成了将用户YANG文件的解析并保存到yang_modules.info的rb_tree中,也完成了用户对yang文件回调的解析保存到snode->priv中,也完成用户自定义的回调的校验,完成上面工作,前期的相关初始化算完成了。
3 北向syrepo初始化分析
1、先看北向sysrepo的初始化入口定义FRR_MODULE_SETUP, 这个处理在前面聊过,可以回头看。
2、再看看hook_register的扩展
/*向hook添加回调*/
extern void _hook_register(struct hook *hook, void *funcptr, void *arg,
bool has_arg, struct frrmod_runtime *module,
const char *funcname, int priority);
#define hook_register(hookname, func) \
_hook_register(&_hook_##hookname, _hook_typecheck_##hookname(func), \
NULL, false, THIS_MODULE, #func, HOOK_DEFAULT_PRIORITY)
static int frr_sr_module_init(void)
{
/*向_hook_frr_late_init,添加frr_sr_module_late_init回调*/
hook_register(frr_late_init, frr_sr_module_late_init);
/*向_hook_frr_very_late_init,添加frr_sr_module_very_late_init回调*/
hook_register(frr_very_late_init, frr_sr_module_very_late_init);
return 0;
}
FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION,
.description = "FRR sysrepo integration module",
.init = frr_sr_module_init, )
综上,frr北向syrepo的初始化入口函数,实质是注册了两个hook函数。
frr_sysrepo的hook回调已经注册上,那么,是如何启动相应的模块的初始化,多说一句的,FRR的全部模块都是使用这一模式初始化的。
再回过头看看isis的main()函数,main()函数在快要结束是调用frr_config_fork()函数,
void frr_config_fork(void)
{
hook_call(frr_late_init, master); //这就对应执行frr_sr_module_late_init初始化
if (!(di->flags & FRR_NO_CFG_PID_DRY)) {
/* Don't start execution if we are in dry-run mode */
if (di->dryrun) {
frr_config_read_in(NULL);
exit(0);
}
thread_add_event(master, frr_config_read_in, NULL, 0,
&di->read_in);
}
if (di->daemon_mode || di->terminal)
frr_daemonize();
if (!di->pid_file)
di->pid_file = pidfile_default;
pid_output(di->pid_file);
zlog_tls_buffer_init();
}
再看看hook_call的执行
#define hook_call(hookname, ...) hook_call_##hookname(__VA_ARGS__)
hook_call实际就是hook_call_frr_late_init(master),也就是执行的frr_sr_module_late_init()
而frr_config_read_in()又注册了hook_call(frr_very_late_init, master);
这个hook与上面的原理一到致,最后调用到frr_sr_module_very_late_init().
因此在main()的config_fork中完成模块的最后初始化。
frr_sr_module_very_late_init()的处理流程
static int frr_sr_module_very_late_init(struct thread_master *tm)
{
master = tm;
if (frr_sr_init() < 0) {
flog_err(EC_LIB_SYSREPO_INIT,
"failed to initialize the Sysrepo module");
return -1;
}
//注册frr_sr_finish的hook,在进程结束退出时用于回收分配的sysrepo资源
hook_register(frr_fini, frr_sr_finish);
return 0;
}
/* FRR's Sysrepo 初始化*/
static int frr_sr_init(const char *program_name)
{
struct yang_module *module;
int ret;
</*frr-sysrepo相当于在sysrepo新创建一个插件,插件如何初始化,可以再回头看看前面的章节./>
/* 与sysrepo完成建立链接并启用一个会话后,需要对相应的回调做相应的订阅.
* yang_modules还是前面nb_init()所解析出并保存在yang_modules rb_tree中的yang_modules.
* 还是一个一个yang module的订阅,完成module_change,state, rpc,action的订阅
/* Perform subscriptions. */
RB_FOREACH (module, yang_modules, &yang_modules)
{
int event_pipe;
frr_sr_subscribe_config(module);
yang_snodes_iterate(module->info, frr_sr_subscribe_state, 0,
module);
yang_snodes_iterate(module->info, frr_sr_subscribe_rpc, 0,
module);
/* Watch subscriptions. */
ret = sr_get_event_pipe(module->sr_subscription, &event_pipe);
if (ret != SR_ERR_OK) {
flog_err(EC_LIB_SYSREPO_INIT,
"%s: sr_get_event_pipe(): %s", __func__,
sr_strerror(ret));
goto cleanup;
}
thread_add_read(master, frr_sr_read_cb, module->sr_subscription,
event_pipe, &module->sr_thread);
}
/*注册nb_notification_send的hook函数*/
hook_register(nb_notification_send, frr_sr_notification_send);
return 0;
}
static void frr_sr_subscribe_config(struct yang_module *module)
{
int ret;
/* frr使用对整个模块的订阅,不具体区分特定的子订阅,这样的操作是,将订阅实现简单我我化.
* 回调frr_sr_config_change_cb是对整个模块变化的响应,
* 基于module的订阅,需要相应的处理的请求就大幅度降低,从而提高了整个sysrepo的处理性能.
*/
ret = sr_module_change_subscribe(
session, module->name, NULL, frr_sr_config_change_cb, NULL, 0,
SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED | SR_SUBSCR_NO_THREAD,
&module->sr_subscription);
if (ret != SR_ERR_OK)
flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s",
sr_strerror(ret));
}
/* state、rpc、action的订阅类似,都与节点类型相关,非不相关的节点,不需要执行相应的订阅
* 以state的订阅为例,
* const struct lys_node *snode //yang文件解析出的数据,
* //包括用户遍历时创建的私有数据,这个就厉
* //害,snode->priv与nb_node相互转化,
* //都是指向同一块内存空间
*/
static int frr_sr_subscribe_state(const struct lys_node *snode, void *arg)
{
struct yang_module *module = arg;
struct nb_node *nb_node;
int ret;
/*节点类型的检查,非config false节点,不需要订阅*/
if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
return YANG_ITER_CONTINUE;
/* We only need to subscribe to the root of the state subtrees. */
if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
return YANG_ITER_CONTINUE;
/*通过snode->priv得到nb_node,nb_node结构中有xpath,就可以相应的路径做state的订阅.*/
nb_node = snode->priv;
DEBUGD(&nb_dbg_client_sysrepo, "%s: providing data to '%s'", __func__,
nb_node->xpath);
ret = sr_dp_get_items_subscribe(
session, nb_node->xpath, frr_sr_state_cb, NULL,
SR_SUBSCR_CTX_REUSE, &module->sr_subscription);
if (ret != SR_ERR_OK)
flog_err(EC_LIB_LIBSYSREPO, "sr_dp_get_items_subscribe(): %s",
sr_strerror(ret));
return YANG_ITER_CONTINUE;
}
通过以上过程,FRR完成对sysrepo的纳管,整个过程中,最重要的是nb_init()、frr_sysrepo()的初始化,nb_init()完成将yang文件映射到内存中,核心的两个结构是yang_modules,snode, nb_node三个数据结构,以及相互的转换,这点处理的非常巧妙,frr_sysrepo()是完成对sysrepo的订阅,以上就是frr对整个sysrepo纳管的初始化流程分析。
而用户在frr_yang_module_info中依照模块YANG定义的回调,又是如何响应与sysrepo处理的实际请求。通俗些,ODL控制器下发一份配置,是怎样通过sysrepo-->frr-sysrepo->isis完成整个过程的处理,就放在下章节再细说。