Sysrepo
Sysrepo for Plugin Developers
https://www.sysrepo.org/plugins/documentation
本文将概述 Sysrepo 及其相关项目,重点介绍插件开发。为了演示在开发插件时如何使用 Sysrepo,我们将以几个现有插件为例进行说明。
What is Sysrepo?
当谈到Sysrepo时,第一个问题就是:Sysrepo究竟是什么?根据官方文档的描述:“Sysrepo是一个基于YANG的数据存储库,用于Unix/Linux系统。”正如其名称所示,数据存储库(datastore)是一种用于存储数据的系统,可以采用多种方式实现,例如通过文件、数据库或其他类型的存储。在本文中,sysrepo存储的数据主要是各种应用程序中的配置数据。例如,DHCP插件使用Sysrepo存储其配置设置。以前,大多数应用程序都有自己的格式和管理配置数据的方法。这种方法的一个明显困难是,在使用新应用程序时需要学习新的格式。为了解决这个问题并提供一种统一的管理配置的方法,Sysrepo使用YANG格式存储应用程序配置。下面是一个来自DHCP插件YANG模型的示例。
leaf start {
type uint32;
Default "100";
}
leaf stop {
must ". >= ../start";
type uint32;
default "250";
}
这个示例定义了将从DHCP服务器分配的IP地址范围。完整的YANG模型随DHCP插件一起提供,可在此处访问。DHCP插件本身并不是DHCP服务器或客户端,而是负责在YANG和UCI配置数据之间进行映射(UCI是OpenWrt特有的配置系统)。然后,系统上的实际DHCP服务器和客户端读取UCI数据并与之交互,而无需知道DHCP插件和Sysrepo的存在。如前所述,DHCP插件(以及本文中提到的所有其他插件)是Sysrepo提供的配置管理统一化的一个例子,这使得与其一起工作变得更加容易。该插件在两个方向上同步更改,如果在UCI配置中进行了更改,它将被转换并存储到Sysrepo数据存储中,反之亦然。
Sysrepo and YANG
一个需要强调的重要区别是,YANG模型并不包含实际的配置数据。如之前的YANG示例所示,YANG模型仅描述了配置数据应该是什么样子,例如地址范围应该从100到250。实际的配置数据将以JSON或XML数据的形式存储,并会包含一个具体的数值。Sysrepo会检查实际数据是否有效,以及它是否符合描述它的YANG模型。要访问YANG模型中的数据,编辑它或只是获取数据,我们需要一种方法来指定我们想要访问哪些数据。如YANG RFC中所描述的,YANG依赖于XML路径语言(XPath)来指定对数据不同部分的引用。Sysrepo也使用XPath来引用数据。XPath的使用得到了Sysrepo用于处理YANG数据的libyang库的支持。
Sysrepo and NETCONF
Sysrepo 的另一个基础系列 RFC 是关于 NETCONF 的。虽然 YANG 专注于描述数据存储库中的数据,但 NETCONF 是描述如何安装、修改和删除网络设备配置数据的协议。目前,Sysrepo 已与 Netopeer2 NETCONF 服务集成,因此使用 Sysrepo 的应用程序(如DHCP, VPP)可以通过 NETCONF 进行管理。因此,开发 Sysrepo 插件需要与 Sysrepo 本身、libyang 以及 Netopeer2 一起工作,linyang 是用于 YANG 的 C 语言库,Netopeer2 提供了 NETCONF 服务器和客户端,并使用 libnetconf2。所有这些项目都是用 C 语言编写的。在开发插件时,最好使用这些项目的最新版本。对于 Sysrepo 来说,版本 0.7 及其更早版本是过时的,不应使用。可以在相应的 GitHub 页面上找到这些项目的最新版本。
接下来的三个部分将提供 YANG 语言、XPath 和 NETCONF 的高级概述,然后进一步解释 Sysrepo 插件的功能及其内部工作原理。
The YANG language
YANG 语言的主要目的是描述我们正在管理的系统的配置和状态。YANG 和 NETCONF 是 IETF 开发的一系列 RFC 的一部分,旨在使管理网络设备更加容易,并提供统一的管理方式。以前,管理网络设备时,需要使用各种特定于厂商的工具。在与 OpenWrt UCI 合作时,主要使用 procd 脚本和 ubus 工具来管理系统。CISCO 设备使用自己的工具,Juniper 使用另一套工具,等等。
IETF 已经为各种用途定义了多种 YANG 模型,例如用于接口管理(interface management)的YANG模型。这些模型以 RFC 的形式发布,旨在在平台和厂商之间保持独立,并标准化对目标系统的管理。其他标准机构和厂商,如 ITU 和 OpenConfig 也在开发开放的 YANG 模型。查看所有已发布的 YANG 模型的方便方式是使用 YANG 目录(YANG Catalog)。该项目(YANG catalog)有一个前端搜索界面,可通过目录内容进行搜索,一个 YANG 验证器,可验证 YANG 模块以及其他一些用于处理 YANG 的有用工具。该目录基于一个GitHub存储库,用于跟踪YANG模型,可在此处访问。当然,任何人都可以开发和使用自己的YANG模型。
YANG basic syntax
像大多数编程语言一样,YANG定义了各种基本数据类型(如整数和字符串)和数据结构(如列表和容器)。它支持从其他模块中导入内容,并可以将单个模块拆分为子模块,然后由主模块包含。再次以DHCP模块为例,可以看到它导入了两个模块,如下所示:
import ietf-inet-types {
prefix inet;
}
import ietf-yang-types {
prefix yang;
}
...
leaf aftr_v4_local {
type inet:ipv4-address-no-zone;
}
导入的模块通过前缀进行引用,如在aftr_v4_local
叶子节点中可以看到,该叶子的类型是在ietf-inet-types模块中定义的。包含(include)的工作方式与此类似,但include包含的是模块的子模块。YANG 将它所描述的数据表示为一棵树,其中每个节点都有一个名称和一个值或子节点。最简单的节点类型是叶子节点,它包含一个单个类型的值。其他常用的类型是container(容器),容器用于将相关的节点组合成子树(subtree),它们没有值,只包含子节点,这个概念与C 语言中的结构类似。leaf-list类型的节点定义了一组相同类型的值。下面的例子展示了container和leaf-list类型在 DHCP 插件模型中的使用。
container domains {
leaf-list domain {
type domain;
}
}
list(列表)用于定义一组节点的序列,这组节点由它们的key节点值进行分组和标识,一个list可以有多个key叶子节点,如下示例所示:
list device {
key "name";
leaf name {
type string;
}
leaf type {
type string;
}
}
指定为key的节点必须存在于list中。
YANG允许用数据模型描述数据的约束条件,例如限制节点值可以采取的有效值集。下面的第一个YANG示例展示了这样的约束。语句must ". >= ../start;"
将stop节点的值约束为必须大于或等于先前定义的start节点值。
模块可以通过使用typedef关键字来定义自己的数据类型。下面的示例展示了来自DHCP插件YANG模型的一个示例。枚举类型与C等语言中的枚举类型类似。
typedef server-state {
type enumeration {
enum "server" {
descrption "enable the server";
}
enum "replay" {
description "replay the server";
}
enum "disabled" {
description "disable the server";
}
}
}
自定义类型必须基于现有类型,但可以进一步限制其包含的值的范围。
正如之前所提到的,这里需要理解的第一个重要概念是,YANG模型用于描述配置和状态数据,但不包含实际的配置数据。配置数据以另一种格式存储,如XML或JSON。配置数据通常包含来自系统配置的实际数据,例如DHCP地址范围、正在使用的DHCP客户端的接口等。状态数据通常是只读数据,例如状态信息,例如接口状态,或者统计信息,例如接口上的丢包数量。YANG模块中的状态数据由config false;
语句确定。DHCP插件模型有两个仅包含状态数据的container(容器),即dhcp-v4-leases和dhcp-v6-leases,它们包含有关IPv4和IPv6活动DHCP租借的信息。
以上涵盖了DHCP YANG模型中使用的所有类型。还有一些其他常用的YANG类型,如groupings(分组),用于定义一组可重用的节点。与容器不同,如果没有被使用,分组不会向数据树中添加节点。分组和容器的另一个区别在于,分组可以在其他地方使用并进行细化。例如,容器可以使用一个分组,然后细化先前定义的节点之一。分组使用“refine”关键字进行细化。语言中类似的功能是使用“augment”关键字来扩展现有的数据模型。它允许一个模块向数据模型中插入额外的节点,无论是自己的还是其他模型的。分组和扩展(augment)都是在与外部模块交互时特别有用。例如,用户可以使用现有的标准化模块,然后编写自己的模块,该模块导入并扩展现有模块,以便将其定制为满足自己的特定需求。
RPC and notifications
除了状态和配置数据外,YANG还支持RPC(远程过程调用)和通知。RPC(远程过程调用)用于描述被描述系统所支持的操作。操作的输入和输出都用YANG进行描述。下面是一个来自generic-sd-bus-plugin的简化示例。
rpc sd-bus-call {
input {
list sd-bus-message {
key "sd-bus sd-bus-service";
leaf sd-bus {
description "sd-bus bus to contact.";
mandatory true;
type enumeration {
enum SYSTEM;
enum USER;
}
}
leaf sd-bus-method {
description "sd-bus method name.";
mandatory true;
type string;
}
leaf sd-bus-arguments {
description "sd-bus method arguments.";
mandatory true;
type string.
}
}
}
}
示例展示了一个RPC语句的输入部分,该语句定义了对Systemd sd-bus的调用。输出部分看起来类似。
通知语句的用法与此类似,用于建模通知,这些通知基本上是事件,基于NETCONF通知。如需了解更多信息,建议阅读YANG 1.1. RFC。
XPATH query language
What is XPath?
XPath 是一种用于从XML文档中检索数据的查询语言。它使用基于XML的抽象树表示法。由于 NETCONF 使用 XML 编码数据,因此 XPath 成为 Sysrepo 和 YANG 的重要组成部分也就不足为奇了。它还提供了以基本方式测试和操作所寻址数据的功能。顾名思义,它使用基于路径的标记来寻址元素。存在多个XPath版本,3.0是最新版本。然而,Sysrepo使用的是1.0版本,因此这里将对其进行描述。大多数XPath处理是由libyang完成的。
XPath in Sysrepo plugins
在开发Sysrepo插件时,可能会遇到XPath的地方有三个:首先,在开发或使用现有的YANG模型时,YANG在must和when等各种语句声明中使用XPath,而augment和leafref等其他声明则使用XPath语言的简化子集。之前在第一个YANG示例中已经展示了一个这样的例子,其中must语句使用XPath从stop节点引用start节点。可以使用简化的XPath来访问YANG模式中的节点。然而,在处理YANG数据时,可以使用几乎完整的XPath语言子集。XPath的第二种用法是在使用libyang和Sysrepo API时,这些API使用XPath。最后,使用sysrepocfg工具在Sysrepo中导入、编辑和导出配置时通常需要使用XPath。
XPath 有四种主要类型,可以作为 XPath 表达式计算的结果返回。首先是各种类型的节点,然后是布尔值、浮点数和字符串。表达式在特定的上下文中进行求值。上下文包含许多元素,包括上下文节点、函数库和一组命名空间声明。还有一些其他元素,但对于Sysrepo插件开发来说,这些元素并不那么重要。函数库包含接收前面提到的四种类型参数并返回结果的函数。对于与YANG一起使用的目的而言,命名空间集是正在使用的模块集合。在此情况下,模块前缀对应于XML命名空间URI。函数库和命名空间集在表达式和子表达式计算中保持不变。然而,上下文节点通常会发生变化。上下文节点通常是正在计算的表达式的节点。与其他路径标记类似,“/”选择树的根节点。同样,与其他路径标记类似,XPath中有两种类型的路径:相对路径和绝对路径。相对路径从上下文节点开始进行计算,而绝对路径以“/”开头,从树根开始进行计算。路径是逐步进行计算的,每一步都可以指定一个更小的路径子集,或者通过类型或使用谓词进一步过滤返回节点的集合。 谓词使用任意表达式进一步过滤节点集。 一些常用的快捷方式,如“*”选择所有节点,“…”选择父节点,“.”选择当前节点以及其他快捷方式都是可用的。
在文档的后续部分将展示XPath的实际应用示例,并演示sysrepocfg工具的使用。 有关XPath的更多详细信息,请参见XPath标准官方文档XPath startard。
在使用XPath和YANG时,请记住可以访问的YANG模型树子集取决于XPath表达式所在的声明的位置。 例如,如果“must”表达式定义在配置节点的一个子声明中,则可以访问的树子集包括所有配置节点。 在状态节点中的表达式可以访问所有配置和状态节点。 YANG RFC还描述了通知和RPC中XPath表达式的其他规则。
NETCONF
What is NETCONF?
NETCONF 是由 IETF 定义的用于在网络设备上安装、操作和删除配置数据的协议。NETCONF 消息被编码为 XML。该协议的通信通过一系列 RPC 实现。它基于服务器-客户端通信模型。服务器通常被称为代理,而客户端则被称为管理器。服务器通常运行在被客户端管理的网络设备上。从概念上讲,NETCONF 可以分为四个层次。第一层是安全传输层,常见的实现方式是使用 SSH 或 TLS。下一层定义了 NETCONF 协议可以传输的一系列可能的消息,包括 RPC、RPC 错误和响应以及通知。消息层启用了下一层,即操作层,其中包含特定的操作,如 <get-config>、<edit-config>、<delete-config> 和 <close-session> 等。正如这些操作名称所暗示的那样,它们主要用于配置和会话管理。当客户端连接到服务器时会打开一个会话。最后是内容层,它没有被 NETCONF RFC 描述,在实践中它包含 YANG 数据。通过使用capabillities(能力),可以扩展支持的操作集。服务器和客户端随后会记录它们支持的非标准功能(capabilities),这样一旦它们开始通信,就可以协商并找出它们共有的功能(capabilities)。与YANG类似,NETCONF也区分状态数据和配置数据。get-config命令仅获取配置数据,而get命令获取两者。NETCONF协议定义了至少一个,通常是多个配置数据存储的存在。最常见的三个数据存储是startup(启动)、running(运行)和candidate(候选)。RFC将配置数据存储定义为“将设备从初始默认状态转换为所需运行状态所需的完整配置数据集”。RFC要求所有NETCONF服务器必须包含running数据存储(datastore)。它包含网络设备当前活动的配置。candidate datastore可以在不影响运行配置的情况下进行操作,并且可以在完成更改后将其提交到running datastore。startup datastore包含在启动过程中加载到设备上的初始配置数据。Sysrepo 使用了所有三个数据存储库,并对其进行了一些更改。由于网络管理数据存储库是一个更通用的概念,因此大多数定义实际上都包含在 NMDA(Network Management Datastore Architecture, 网络管理数据存储库架构)RFC 中。RFC 定义了一个通用的体系结构模型,它不一定与 NETCONF 绑定,但大多数 NETCONF 服务都遵循它。
Sysrepo plugin architecture
What are Sysrepo plugins?
使用Sysrepo本身和开发使用Sysrepo的应用程序主要有两种方法:直接方法和间接方法。直接方法是指在应用程序中随时调用Sysrepo函数以获取配置数据或执行特定的回调以响应配置更改。间接方法是指编写一个独立的守护进程,将Sysrepo调用转换为特定于应用程序的操作。这种间接方法通常更适合现有应用程序,因为它们无需更改自身即可使用Sysrepo数据存储,代价是增加了一个中间过程(守护进程)。这两种方法如图所示。
如果有多个采用间接方法编写的守护进程,可以将它们编写为插件,然后由一个进程统一管理,例如sysrepo-plugind守护进程。这个守护进程是一个简单的守护进程,它将所有可用的Sysrepo插件组合到一个单独的进程中。
Initialization and connecting to Sysrepo
鉴于Sysrepo应用程序可以以插件的形式编写(间接方法),或者以守护进程的形式编写(直接方法),如果可能的话,编写支持这两种方法的应用程序可能是明智的。使用间接方式的Sysrepo插件通过向sysrepo-plugin守护进程暴露sr_plugin_init_cb
和sr_plugin_cleanup_cb
函数进行初始化和清理。然而,通过将这些函数作为应用程序主入口函数的一部分来调用,可以将代码作为守护进程运行。例如,下面展示了DHCP插件初始化代码的片段。
int sr_plugin_init_cb(sr_session_ctx_t *session, void **private_data)
{
...
}
void sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *private_data)
{
...
}
#ifndef PLUGIN
#include <signal.h>
#include <unistd.h>
int main()
{
(...)
error = sr_plugin_init_cb(session, &private_data);
if (error)
goto out;
/* loop until ctrl-c is pressed / SIGINT is received */
signal(SIGINT, sigint_handler);
signal(SIGPIPE, SIG_IGN);
while(!exit_application) {
sleep(1); /* or do same more useful work... */
}
out:
sr_plugin_cleanup_cb(session, private_data);
sr_disconnect(connection);
return error ? -1 : 0;
}
(...)
#endif
从上面的初始化代码可以看出,有一个#ifndef PLUGIN
的保护块,允许插件以插件的形式加载或以守护进程应用程序的形式运行。这个定义保护块是在构建时通过相应的构建系统标志选项确定的。在这个特定的例子中,使用了CMake构建系统,允许传递诸如-DPLUGIN=TRUE
的标志。上述变量将构建相应的代码作为用于sysrepo-plugind的共享对象文件(即插件)。同样,当-DPLUGIN=FALSE时,构建系统输出将是一个可直接执行的单个二进制文件。
使用 Sysrepo 的应用程序或插件本身通过某些初始化步骤与 Sysrepo 数据库进行交互。使用直接方式编写的应用程序必须首先使用 sr_connect
API 调用连接到 Sysrepo。该连接应是每个应用程序独一无二的,通常会持续到程序结束,尽管每个应用程序可以有多个连接。成功连接到 Sysrepo 后,应用程序还应通过这个连接调用 sr_session_start
API 函数来启动会话。可以创建任意数量的会话。最重要的是,在使用此 API 时需要考虑线程模型。Sysrepo 对会话没有内在的限制,因为每个会话只需要少量资源,拥有许多会话也不会造成任何问题。会话必须通过选择适当的 Sysrepo 数据存储进行初始化。
Sysrepo database
Sysrepo 并未实现 NMDA RFC 的全部内容。它完全支持startup(启动)、running(运行)、candidate(候选)和operational的数据存储。startup、running和candidate数据存储的工作方式与 NETCONF 部分所述相同。operational数据存储对应于running数据存储的一部分,并增加了状态数据。它是只读的并且默认为空,添加到其中的数据要么是运行数据,要么是来自订阅的数据。
Connecting to Sysrepo datastores
如前所述,Sysrepo sr_session_start
API调用要求用户选择用户想要连接的数据存储。当使用直接方式编写应用程序时,当用户连接到running数据存储时,可能会使用下面显示的代码:
int main()
{
int error = SR_ERR_OK;
sr_conn_ctx_t *connection = NULL;
sr_session_ctx_t *session = NULL;
void *private_data = NULL;
/* connect to sysrepo */
error = sr_connect(SR_CONN_DEFAULT, &connection);
if (error)
goto out;
error = sr_session_start(connection, SR_DS_RUNNING, &session);
if (error)
goto out;
error = sr_plugin_init_cb(session, &private_data);
if (error)
goto out;
(...)
out:
sr_plugin_cleanup_cb(session, private_data);
sr_disconnect(connection);
return error ? -1 : 0;
}
sr_plugin_init_cb
函数的实现对于直接和间接方式来说是相同的。当Sysrepo插件被初始化时,管理守护进程应该传递适当的会话上下文。从该会话中,可以通过调用sr_session_get_connection
获取连接,并使用sr_session_start
API在该连接上创建会话。下面是一个此类过程的示例。
int sr_plugin_init_cb(sr_session_ctx_t *session, void **private_data)
{
int error = 0;
sr_conn_ctx_t *connection = NULL;
sr_session_ctx_t *startup_session = NULL;
sr_subscription_ctx_t *subscription = NULL;
*private_data = NULL;
connection = sr_session_get_connection(session);
error = sr_session_start(connection, SR_DS_STARTUP, &startup_session);
if (error)
goto out;
*private_data = startup_session;
if (running_datastore_is_empty == true) {
//do something e.g.
//sr_copy_config(startup_session, DHCP_YANG_MODEL, SR_DS_RUNNING, 0, 0);
}
out:
return error ? SR_ERR_CALLBACK_FAILED : SR_ERR_OK;
}
从以上代码可以看出,新创建的会话与startup数据存储相关联,即它使用SR_DS_STARTUP
调用sr_session_start
。
Sysrepo subscriptions
在建立连接并建立所需会话之后,sysrepo插件的下一步通常是订阅与之协同工作的YANG模型的不同部分。有四种类型的订阅,以及与之对应的四种函数调用。一种用于配置部分模型的变化,一种用于RPC,一种用于通知,最后一种用于状态数据。当进行订阅时,会注册一个回调函数。当发生更改时,该回调函数将被调用。订阅通常在插件初始化时注册。
Module change subscriptions
用于订阅配置更改的函数是 sr_module_change_subscribe
,下面是一个其用法的示例。
SRP_LOG_INFMSG("subscribing to module change");
error = sr_module_change_subscribe(session, DHCP_YANG_MODEL, "/" DHCP_YANG_MODEL ":*//*", dhcp_module_change_cb, *private_data, 0, SR_SUBSCR_DEFAULT, &subscription);
if (error) {
SRP_LOG_ERR("sr_module_change_subscribe error (%d): %s", error, sr_strerror(error));
goto error_out;
}
第一个参数是之前已初始化的会话,接下来是YANG模型,在此示例中为 terastream-dhcp。第三个参数是XPath,指定应跟踪数据模型中哪个部分的变化,在此示例中为整个模型。接下来是当发生更改时将被调用的回调函数。下一个参数是优先级,它确定在同一模块中回调函数的调用顺序。由于本例只有一个回调函数,因此值为0。最后是订阅选项和订阅上下文。在此使用的订阅选项设置为默认的SR_SUBSCR_DEFAULT
值。订阅选项可以启用各种功能,例如是否可以重复使用单个订阅上下文以进行多次订阅、是否可以接收SR_EV_UPDATE
事件等等。
注册的订阅回调会在发生更改时被调用。默认情况下有两个事件,但可以有更多。两个默认事件是SR_EV_CHANGE
,表示更改第一次出现时触发的事件,以及SR_EV_DONE
,表示更改已提交时触发的事件。还有一个事件是SR_EV_ABORTED
。下面是一个事件处理的示例。
if (event == SR_EV_ABORT) {
SRP_LOG_ERR("aborting changes for: %s", xpath);
error = -1;
goto error_out;
}
if (event == SR_EV_DONE) {
error = sr_copy_config(startup_session, DHCP_YANG_MODEL, SR_DS_RUNNING, 0, 0);
if (error) {
SRP_LOG_ERR("sr_copy_config error (%d): %s", error, sr_strerror(error));
goto error_out;
}
if (event == SR_EV_CHANGE) {
error = sr_get_changes_iter(session, xpath, &dhcp_server_change_iter);
if (error) {
SRP_LOG_ERR("sr_get_changes_iter error (%d): %s", error, strerror(error));
goto error_out;
}
}
...
}
我们可以看到,回调需要检查它收到了哪种类型的事件,然后处理该事件。如果收到SR_EV_CHANGE
事件,这意味着更改已经到达,因此需要对其进行解析并适当地处理。如果收到SR_EV_DONE
事件,则意味着更改已经提交,因此需要将running数据存储中的更改复制到startup数据存储中。在此情况下,startup_session
会话已经使用startup数据存储进行初始化。
Operational subscriptions
Operational订阅用于在客户端请求时提供数据。它们与operational数据存储一起工作。要注册operational订阅,可以使用 sr_oper_get_items_subscribe
函数。下面是一个来自 DHCP 插件的示例。
SRP_LOG_INFMSG("subscribing to get items");
error = sr_oper_get_items_subscribe(session, DHCP_YANG_MODEL, DHCP_V4_STATE_DATA_PATH, dhcp_state_data_cb, NULL, SR_SUBSCR_CTX_REUSE, &subscription);
if (error) {
SRP_LOG_ERR("sr_oper_get_items_subscribe error (%d): %s", error, strerror(error));
goto error_out;
}
error = sr_oper_get_items_subscribe(session, DHCP_YANG_MODEL, DHCP_V6_STATE_DATA_PATH, dhcp_state_data_cb, NULL, SR_SUBSCR_CTX_REUSE, &subscription);
if (error) {
SRP_LOG_ERR("sr_oper_get_items_subscribe error (%d): %s", error, strerror(error));
goto error_out;
}
该函数签名与模块更改订阅函数类似。第一个参数仍然是会话,然后是模型、XPath、回调函数、私有数据、订阅选项和订阅上下文。可以看出,模块更改订阅上下文在这里被重用了。回调的开始部分如下所示。
static int dhcp_state_data_cb(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)
{
srpo_ubus_result_values_t *values = NULL:
srpo_ubus_call_data_t ubus_call_data = {.lookup_path = NULL, .method = NULL, .transform_data_cb = NULL};
int error = SRPO_UBUS_ERR_OK;
if (!strcmp(path, DHCP_V4_STATE_DATA_PATH) || !strcmp(path, "*")) {
srpo_ubus_init_result_values(&values);
error = srpo_ubus_call(values, &ubus_call_data);
if (error != SRPO_UBUS_ERR_OK) {
SRP_LOG_ERR("srpo_ubus_call error (%d): %s", error, srpo_ubus_error_description_get(error));
goto out;
}
error = store_ubus_values_to_datastore(session, request_xpath, values, parent);
if (error) {
SRP_LOG_ERR("store_ubus_values_to_datastore error (%d)", error);
goto out;
}
srpo_ubus_free_result_values(values);
values = NULL;
}
if (!strcmp(path, DHCP_v6_STATE_DATA_PATH) || !strcmp(path, "*"))
{
...
}
...
}
正如前两个例子所示,这里有两个单独的情况需要处理。其中一个案例处理DHCPv6状态数据,另一个案例处理DHCPv4状态数据。之所以需要进行分离,是因为回调函数需要为两个不同的DHCP版本提供并处理不同的状态数据。
RPC订阅和通知的工作方式类似。RPC订阅注册函数是sr_rpc_subscribe_tree
。RPC回调函数接收RPC输入,然后调用RPC并填充输出YANG树。generic-sd-bus-plugin
中提供了一个示例。不带树(tree)后缀的变体不应使用,因为它仅出于历史原因而存在。
Sysrepo Utilities
本节介绍各种工具软件,以使在使用Sysrepo插件、Sysrepo和YANG时更加方便。
Sysrepoctl
sysrepoctl的主要目的是允许对YANG模块进行修改。它可以在系统上列出、安装、删除和更改各种YANG模块的属性。
使用命令sysrepoctl -l
可以显示系统上已安装的YANG模块。示例结果如下:
要安装一个模块,例如DHCP插件所需的terastream-dhcp YANG模块,应该使用sysrepoctl -i ./
terastream-dhcp@2017-12-07.yang`命令。系统上再次列出模块的结果如下所示。
要删除一个模块,例如同样的terastream-dhcp模块,应该使用-u标志,像这样:sysrepoctl -u terastream-dhcp
。但是,这次参数是模块名称,而不是安装时使用的路径。
还有其他可用的标志,例如,-c 标志用于更改各种模块属性。在示例中,snabb 插件的 ietf-softwire-br
模型需要启用绑定模式。为了实现这一点,可以使用以下命令:sysrepoctl -c ietf-softwire-br -e binding-mode
。执行该命令后,sysrepoctl 输出列表中的“Features”列应该显示 binding-mode 作为 ietf-softwire-br 的新功能。如果一个模块导入了另一个模块,则必须先安装导入的模块。例如,如果安装了 ieft-softwire-br 而没有安装 ietf-softwire-common 模块,将会出现下面显示的错误。
更多关于sysrepoctl
命令的信息可以通过运行sysrepoctl -h
命令或在官方文档中找到。
Sysrepocfg
sysrepocfg
工具允许在Sysrepo中操作配置数据。它支持为特定数据存储和模型导入、导出和修改数据。它还可以用于发送通知和RPC。下面展示了一个导出配置的示例命令。-X
标志告诉sysrepocfg
将配置导出到标准输出(stdout)。-d
标志用于选择数据存储,在这个例子中,将选择启动数据存储(startup datastore)。-f
标志用于选择配置输出的格式,可以是XML或JSON。最后,我们可以使用-m
来选择一个模型。
配置可以通过使用 -E
标志进行编辑,并通过 -I
标志进行导入。除了导出、导入或编辑整个配置文件之外,还可以通过 -x
标志指定树中子集的 xpath 来编辑树中的子集。下面是一个示例:
编辑配置
sysrepocfg -E -d running -m <model-name> -x /<xpath-to-subset>
假设我们想要编辑的 xpath 是 /ietf-interfaces:interfaces/interface[name=‘eth0’]/type , 那么命令可能是这样的:
sysrepocfg -E -d running -m ietf-interfaces -x /ietf-interfaces:interfaces/interface[name='eth0']/type
导入配置(通常是在编辑后) ,假设你有一个编辑过的文件 my-config.xml
sysrepocfg -I -d running -m <model-name> -f xml my-config.xml
如果只想导入 xpath 指定的部分,你可以结合使用 -x 标志 ,例如,只导入 /ietf-interfaces:interfaces/interface[name=‘eth0’] 的部分
sysrepocfg -I -d running -m ietf-interfaces -x /ietf-interfaces:interfaces/interface[name='eth0'] -f xml my-partial-config.xml`
请将 <model-name>
替换为你想要操作的模型名称,<xpath-to-subset>
替换为你想要编辑或导入的树中子集的具体 xpath 表达式。同时,如果导入的文件是 JSON 格式,需要将 -f xml
替换为 -f json
。
通过运行带有 -h
帮助标志的 sysrepocfg
命令,以及在官方文档中,可以获得更多信息。这些资源将提供关于 sysrepocfg
的详细用法、选项和示例,帮助用户更好地理解和使用该工具。