Ngsdn-Tutorial-Ⅱ
练习2-Yang,OpenConfig和gNMI基础
说明:本教程翻译自Next-Gen SDN Tutorial的Exercise 2 - Yang, OpenConfig, and gNMI basics,自用学习Docker-P4-Tutorial
这套练习旨在让您更多地了解YANG,OpenConfig和gNMI。
此练习分为三个部分:
- 了解YANG语言
- 了解YANG编码
- 了解启用YANG的传输协议(使用gNMI)
第1部分:了解YANG语言
我们将从一个简单的YANG模块:demo-port
中的yang/demo-port.yang
开始
看一下模型并尝试推导结构。每个叶节点的有效值是多少?
leaf speed {
type identityref {
base demo-port:SPEED;
}
description "Configurable speed of a switch port";
}
该模型是自包含的,因此阅读起来并不是很困难。但是,大多数YANG模型都是定义在许多文件上的,这使得想象出整体结构非常复杂。
为了简化此过程,我们可以使用一个名为pyang
的工具来尝试可视化模型的结构。
首先输入yang-tools的Docker:
$ make yang-tools
bash-4.4#
接下来,在demo-port.yang
模型上运行pyang
:
bash-4.4# pyang -f tree demo-port.yang
您应该看到demo-port
模块的树表示形式。这符合您的期望吗?
Extra credit:如果您快速完成此操作,则可以尝试将新的叶子节点添加到port-config
或port-state
分组,然后重新运行pyang
并查看新叶子的添加位置。
我们还可以pyang
用来可视化更复杂的模型集,例如Stratum使用的OpenConfig模型集。
这些模型已经加载到目录中的yang-tools
容器中/models
。
bash-4.4# pyang -f tree \
-p ietf \
-p openconfig \
-p hercules \
openconfig/interfaces/openconfig-interfaces.yang \
openconfig/interfaces/openconfig-if-ethernet.yang \
openconfig/platform/* \
openconfig/qos/* \
openconfig/system/openconfig-system.yang \
hercules/openconfig-hercules-*.yang | less
您应该看到中显示的模型的树结构less
。您可以使用箭头键或j
/k
上下滚动。键入q
退出。
在接口模型中,我们可以看到启用或禁用接口的路径:interfaces/interface[name]/config/enabled
读取接口上传的入数据包数量(in-pkts
)的路径是什么?
Extra credit:如果您有时间,请查看/models
目录中的模型或在Github上浏览它们。
尝试找到enabled
或in-pkts
叶子节点的描述。
// 接口上接收到的数据包总数,包括所有单播,多播,广播和错误数据包等。
leaf in-pkts {
type oc-yang:counter64;
description
"The total number of packets received on the interface,
including all unicast, multicast, broadcast and bad packets
etc.";
reference
"RFC 2819: Remote Network Monitoring Management Information
Base";
}
// 该叶子包含接口的已配置期望状态。
// 如RFC 2863中所述,在初始化ifEntry之后,
// 实现IF-MIB的系统使用“运行中”数据存储区中该叶子的值来将
// IF-MIB.ifAdminStatus设置为“up”或“down”。
// “正在运行”的数据存储区中的“叶”反映在ifAdminStatus中,
// 但是如果通过SNMP更改了“ifAdminStatus”,则此叶不受影响。
leaf enabled {
type boolean;
default "true";
description
"This leaf contains the configured, desired state of the
interface.
Systems that implement the IF-MIB use the value of this
leaf in the 'running' datastore to set
IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
has been initialized, as described in RFC 2863.
Changes in this leaf in the 'running' datastore are
reflected in ifAdminStatus, but if ifAdminStatus is
changed over SNMP, this leaf is not affected.";
reference
"RFC 2863: The Interfaces Group MIB - ifAdminStatus";
}
提示:查看openconfig-interfaces.yang
文件。
第2部分:了解YANG编码
YANG数据编码没有标准,但是可以将遵循YANG模型将数据编码为XML、JSON或Protobuf(以及其他格式)。这些格式都有自己的编码规范。
首先,我们看一下YANG的第一个XML格式规范表示。要查看以XML编码的数据的空骨架,请运行:
bash-4.4# pyang -f sample-xml-skeleton demo-port.yang
该骨架应与我们在第1部分中看到的树表示形式匹配。
YANG-XML-Skeleton
我们还可以使用pyang
基于YANG模型生成DSDL模式:
bash-4.4# pyang -f dsdl demo-port.yang | xmllint --format -
模式的第一部分描述了树结构,第二部分描述了叶节点的值约束。
Extra credit:尝试添加新的速度标识(例如SPEED_100G
)或更改中的port-number
值范围demo-port.yang
,然后重新运行pyang -f dsdl
。您是否看到您的更改反映在DSDL模式中?
接下来,我们将研究使用协议缓冲区(protobuf)编码数据。protobuf编码是一种比XML更紧凑的二进制编码,并且可以自动为数十种语言生成库。我们可以使用ygot
的proto_generator
产生从我们的洋型号protobuf的消息。
bash-4.4# proto_generator -output_dir=/proto -package_name=tutorial demo-port.yang
proto_generator
将生成两个文件:
/proto/tutorial/demo_port/demo_port.proto
/proto/tutorial/enums/enums.proto
使用less
打开demo_port.proto
:
bash-4.4# less /proto/tutorial/demo_port/demo_port.proto
该文件包含与YANG模型中定义的结构匹配的top-level Ports消息。您可以看到,proto_generator
它还向每个protobuf消息字段添加了一个自定义选项yext.schemapath
,该字段显式映射到YANG叶路径。枚举(如tutorial.enums.DemoPortSPEED
)不包含在此文件中,proto_generator
将它们放在单独的enums.proto
文件中:
使用less
打开enums.proto
:
bash-4.4# less /proto/tutorial/enums/enums.proto
如果您完成刚才的Extra credit,您应该会看到10GB speed的枚举以及添加的任何其他速度。
我们还可以proto_generator
用来为Stratum使用的OpenConfig模型构建protobuf消息:
bash-4.4# proto_generator \
-generate_fakeroot \
-output_dir=/proto \
-package_name=openconfig \
-exclude_modules=ietf-interfaces \
-compress_paths \
-base_import_path= \
-path=ietf,openconfig,hercules \
openconfig/interfaces/openconfig-interfaces.yang \
openconfig/interfaces/openconfig-if-ip.yang \
openconfig/lacp/openconfig-lacp.yang \
openconfig/platform/openconfig-platform-linecard.yang \
openconfig/platform/openconfig-platform-port.yang \
openconfig/platform/openconfig-platform-transceiver.yang \
openconfig/platform/openconfig-platform.yang \
openconfig/system/openconfig-system.yang \
openconfig/vlan/openconfig-vlan.yang \
hercules/openconfig-hercules-interfaces.yang \
hercules/openconfig-hercules-platform-chassis.yang \
hercules/openconfig-hercules-platform-linecard.yang \
hercules/openconfig-hercules-platform-node.yang \
hercules/openconfig-hercules-platform-port.yang \
hercules/openconfig-hercules-platform.yang \
hercules/openconfig-hercules-qos.yang \
hercules/openconfig-hercules.yang
您将在目录/proto/openconfig
中找到openconfig.proto
和enums.proto
。
Extra credit:尝试查找用于启用端口的Protobuf消息字段或获取Protobuf消息中的入口数据包计数器。
提示:通过schemapath搜索可能会有所帮助。
ygot
也可以用来生成符合YANG模型的Go结构,并能够验证数据的结构,类型和值。
Extra credit:如果您有多余的时间或对一起使用yang和Go感兴趣,请尝试为该demo-port
模块生成Go代码。
bash-4.4# mkdir -p /goSrc
bash-4.4# generator -output_dir=/goSrc -package_name=tutorial demo-port.yang
看看中的Go文件/goSrc
。
现在,您可以退出容器(使用Ctrl-D
或exit
)。
第3部分:了解启用YANG的传输协议
有几种YANG模型不可知协议,可用于获取或设置符合模型的数据,例如NETCONF,RESTCONF和gNMI。
本部分重点介绍通过gNMI使用protobuf编码。
首先,请确保您的Mininet容器仍在运行。
$ make start
docker-compose up -d
mininet is up-to-date
onos is up-to-date
如果您看到以下输出,则表明Mininet没有运行:
Starting mininet ... done
Starting onos ... done
您将需要回到练习1并安装转发规则,以在此练习之间h1a
以及h1b
之后的部分重新建立ping。
接下来,我们将使用gNMI客户端CLI从Mininet网络中的Stratum交换机leaf1
读取所有配置:
$ util/gnmi-cli --grpc-addr localhost:50001 get /
输出的第一部分显示了CLI发出的请求:
REQUEST
path {
}
type: CONFIG
encoding: PROTO
请求的路径是空路径(这意味着配置树的根),数据类型只是配置树,请求的响应编码为protobuf。
输出的第二部分显示了Stratum的响应:
RESPONSE
notification {
update {
path {
}
val {
any_val {
type_url: "type.googleapis.com/openconfig.Device"
value: \252\221\231\304\001\... TRUNCATED
}
}
}
}
您可以看到Stratum提供了type的响应openconfig.Device
,这是在中定义的顶级消息openconfig.proto
。响应是基于protobuf消息的数据的二进制编码。
该值不是很容易让人理解,但是我们可以使用实用程序来转换答复,该实用程序可以在protobuf消息的二进制表示形式和文本表示形式之间进行转换。
我们可以重新运行该命令,但这一次将输出通过转换器实用程序传递less
给管道(然后通过管道输出使滚动更容易):
$ util/gnmi-cli --grpc-addr localhost:50001 get / | util/oc-pb-decoder | less
响应的内容应该更容易阅读。向下滚动到第一个interface
。接口启用了吗?端口的速度是多少?
Extra credit:您能找到in-pkts
吗?如果没有,您为什么认为它们不见了?
gNMI的好处之一是它的“无模式”编码,它允许客户端或设备仅更新需要更新的路径。这对于订阅特别有用。
首先,让我们通过请求leaf1
和h1a
之间的配置端口来尝试无模式表示:
$ util/gnmi-cli --grpc-addr localhost:50001 get \
/interfaces/interface[name=leaf1-eth3]/config
您应该看到此响应在config- enabled和 health-indicator下包含2个叶子:
RESPONSE
notification {
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "config"
}
elem {
name: "enabled"
}
}
val {
bool_val: true
}
}
}
notification {
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "config"
}
elem {
name: "health-indicator"
}
}
val {
string_val: "GOOD"
}
}
}
无模式的表示形式为每个包含路径的叶子提供并update
了叶子的值。您可以确认该接口已启用(设置为true
)。
接下来,我们将为leaf1
连接到h1a
(端口3)的接口订阅入口单播数据包计数器:
$ util/gnmi-cli --grpc-addr localhost:50001 \
--interval 1000 sub-sample \
/interfaces/interface[name=leaf1-eth3]/state/counters/in-unicast-pkts
输出的第一部分显示了CLI发出的请求:
REQUEST
subscribe {
subscription {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "state"
}
elem {
name: "counters"
}
elem {
name: "in-unicast-pkts"
}
}
mode: SAMPLE
sample_interval: 1000
}
updates_only: true
}
我们具有订阅路径,订阅的类型(采样)和采样率(每1000ms或1s)。
输出的第二部分是响应流:
RESPONSE
update {
timestamp: 1567895852136043891
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "state"
}
elem {
name: "counters"
}
elem {
name: "in-unicast-pkts"
}
}
val {
uint_val: 1592
}
}
}
每个响应都有一个时间戳、路径和新值。因为我们正在采样,所以您应该看到每秒打印一次新的更新。保持运行状态,这时候我们也产生一些流量。
在另一个窗口中,打开Mininet CLI并开始ping:
$ make mn-cli
*** Attaching to Mininet CLI...
*** To detach press Ctrl-D (Mininet will keep running)
mininet> h1a ping h1b
在第一个窗口中,uint_val
当ping仍在运行时,您应该看到每秒增加1。(如果不完全是1,那么可能会有其他流量,例如NDP消息导致流量增加。)
您可以使用Ctrl-C
停止gNMI订阅。
最后,我们将使用gNMI的变更订阅功能监视链接事件。
为第一个交换机的第一个端口的操作状态启动订阅:
$ util/gnmi-cli --grpc-addr localhost:50001 sub-onchange \
/interfaces/interface[name=leaf1-eth3]/state/oper-status
您应该立即看到一个响应,指示端口1为UP
:
RESPONSE
update {
timestamp: 1567896668419430407
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "state"
}
elem {
name: "oper-status"
}
}
val {
string_val: "UP"
}
}
}
在运行Mininet CLI的Shell中,让我们断开leaf1
连接到h1a
的接口:
mininet> sh ifconfig leaf1-eth3 down
您应该在gNMI CLI窗口中看到一个响应,显示leaf1
连接h1a
的接口是DOWN
:
RESPONSE
update {
timestamp: 1567896891549363399
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "state"
}
elem {
name: "oper-status"
}
}
val {
string_val: "DOWN"
}
}
}
我们可以使用以下Mininet命令恢复界面:
mininet> sh ifconfig leaf1-eth3 up
您应该在gNMI CLI窗口中看到另一个响应,指示该接口为UP
。
Extra credit:我们还可以使用gNMI禁用或启用接口。
保留您的gNMI订阅以运行操作状态更改。
在Mininet CLI中,在两个主机之间启动ping操作。
mininet> h1a ping h1b
您应该看到在Mininet CLI中显示了答复。
在第三个窗口中,我们将使用gNMI CLI将enabled
叶子的配置值从true
更改为false
。
$ util/gnmi-cli --grpc-addr localhost:50001 set \
/interfaces/interface[name=leaf1-eth3]/config/enabled \
--bool-val false
在gNMI设置窗口中,您应该看到指示enabled
叶子的新值的请求:
REQUEST
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "config"
}
elem {
name: "enabled"
}
}
val {
bool_val: false
}
}
在gNMI订阅窗口中,您应该看到一个新的响应,指示的运行状态leaf1-eth3
为DOWN
:
RESPONSE
update {
timestamp: 1567896891549363399
update {
path {
elem {
name: "interfaces"
}
elem {
name: "interface"
key {
key: "name"
value: "leaf1-eth3"
}
}
elem {
name: "state"
}
elem {
name: "oper-status"
}
}
val {
string_val: "DOWN"
}
}
}
在Mininet CLI窗口中,您应该观察到ping命令已停止工作。
接下来,我们可以重新启用端口:
$ util/gnmi-cli --grpc-addr localhost:50001 set \
/interfaces/interface[name=leaf1-eth3]/config/enabled \
--bool-val true
您应该在gNMI订阅窗口中看到另一个更新,指示该接口是UP
,并且ping应该在Mininet CLI窗口中恢复。
恭喜你!
您已经完成了第二个练习!