01-gRPC配置
本章节下载: 01-gRPC配置 (600.42 KB)
gRPC(Google Remote Procedure Call,Google远程过程调用)是Google发布的基于HTTP 2.0协议承载的高性能开源软件框架,提供了支持多种编程语言的、对网络设备进行配置和管理的方法。通信双方可以基于该软件框架进行二次开发。
gRPC协议栈分层如表1-1所示。
表1-1 gRPC协议栈分层模型
分层 |
说明 |
内容层 |
业务模块的数据 通信双方需要了解彼此的数据模型,才能正确交互信息 |
Protocol Buffers编码层 |
gRPC通过Protocol Buffers编码格式承载数据 |
gRPC层 |
远程过程调用,定义了远程过程调用的协议交互格式 |
HTTP 2.0层 |
gRPC承载在HTTP 2.0协议上 |
TCP层 |
TCP连接提供面向连接的、可靠的数据链路 |
如图1-1所示,gRPC网络采用客户端/服务器模型,使用HTTP 2.0协议传输报文。
图1-1 gRPC网络架构
gRPC网络的工作机制如下:
(1) 服务器通过监听指定服务端口来等待客户端的连接请求。
(2) 用户通过执行客户端程序登录到服务器。
(3) 客户端调用.proto文件提供的gRPC方法发送请求消息。
(4) 服务器回复应答消息。
H3C设备支持作为gRPC服务器或者gRPC客户端。
.proto文件使用protocol buffers语言编写。protocol buffers是Google开发的数据描述语言,用于自定义数据结构并生成基于各种语言的代码,在序列化和结构化数据方面比XML语言更简单、解析更快。
Telemetry是一项监控设备性能和故障的远程数据采集技术。H3C的Telemetry技术采用gRPC协议将数据从设备推送给网管的采集器。如图1-2所示,网络设备和网管系统建立gRPC连接后,网管可以订阅设备上指定业务模块的数据信息。
图1-2 基于gRPC的Telemetry技术
图1-2中,设备支持以下两种gRPC对接模式:
· Dial-in模式:设备作为gRPC服务器,采集器作为gRPC客户端。由采集器主动向设备发起gRPC连接并订阅需要采集的数据信息。
Dial-in模式支持以下操作:
¡ Get操作:获取设备运行状态和运行配置,以及向设备订阅事件。
¡ gNMI(gRPC Network Management Interface,gRPC网络管理接口)类操作,具体包括:
- gNMI Capabilities操作:获取设备的能力集。
- gNMI Get操作:获取设备运行状态和运行配置。
- gNMI Set操作:向设备下发配置。
- gNMI Subscribe操作:向设备订阅数据推送服务,包括事件触发类数据和周期采样类数据。
¡ CLI操作:向设备下发命令行。
· Dial-out模式:设备作为gRPC客户端,采集器作为gRPC服务器。设备主动和采集器建立gRPC连接,将设备上配置的订阅数据推送给采集器。
根据对编码格式支持能力的不同,Telemetry数据模型分为以下两种类型:
· 三层Telemetry数据模型。该模型下,数据经过以下三个层次的处理。
¡ RPC层:定义在公共proto文件grpc_dialout.proto和grpc_dialout_v3.proto中,提供消息格式等公共RPC方法。
¡ Telemetry层:定义在公共proto文件telemetry.proto中,提供数据采样相关服务。
¡ 内容层:可承载GPB或JSON编码格式的业务数据。
RPC层和Telemetry层在gRPC协议栈中属于gRPC层。
· 二层Telemetry数据模型。该模型下,数据仅经过RPC层和内容层两个层次处理,其中内容层的业务数据只支持JSON编码。
gNMI(gRPC Network Management Interface,gRPC网络管理接口)是基于gRPC框架开发的一种操作协议,定义了一系列用于设备状态获取和配置操作的RPC方法。
由于gNMI协议的通信报文与原gRPC有差异,因此,当采集器使用gNMI协议时,设备需要配合使用gNMI模式的订阅来上送对应格式的报文。
与gRPC相关的协议规范有:
· RFC 7540 - Hypertext Transfer Protocol version 2 (HTTP/2)
仅非FIPS模式支持配置gRPC特性。有关FIPS的介绍,请参见“安全配置指导”中的“FIPS”。
如果执行undo grpc enable命令关闭gRPC功能,所有gRPC相关配置都会被删除。
如果没有配置设备发送订阅报文的源地址(source-address命令),则Dial-out模式下推送给采集器的信息中,deviceIpAddr字段将显示为not-config。
请根据实际组网情况选择配置Dial-in模式或Dial-out模式。
(1) 配置gRPC Dial-in模式
设备作为gRPC服务器、采集器作为gRPC客户端的组网中,在设备上进行如下配置:
a. 配置gRPC服务
b. 配置gRPC用户
c. (可选)开启gRPC Dial-in模式的日志功能
(2) 配置gRPC Dial-out模式
设备作为gRPC客户端、采集器作为gRPC服务器的组网中,在设备上进行如下配置:
a. 开启gRPC功能
b. 配置传感器
c. 配置采集器
d. 配置订阅
e. (可选)开启gRPC Dial-out模式的日志功能
(3) (可选)配置设备与采集器之间的安全通信
(4) (可选)配置gRPC的CPU最大占用率
(1) 进入系统视图。
system-view
(2) 开启gRPC功能。
grpc enable
缺省情况下,gRPC功能处于关闭状态。
(3) (可选)配置gRPC服务的端口号。
grpc port port-number
缺省情况下,gRPC服务的端口号为50051。
grpc idle-timeout minutes
缺省情况下,gRPC会话超时时间为5分钟。
设备上需要为gRPC客户端创建本地用户,gRPC客户端才能与设备建立gRPC会话。
(1) 进入系统视图。
system-view
(2) 添加设备管理类本地用户。
local-user user-name [ class manage ]
(3) 设置本地用户的密码。
password [ { hash | simple } password ]
缺省情况下,不存在本地用户密码,即本地用户认证时无需输入密码,只要用户名有效且其他属性验证通过即可认证成功。
(4) 配置本地用户的授权用户角色为network-admin。
authorization-attribute user-role user-role
缺省情况下,本地用户的授权用户角色为network-operator。
(5) 配置本地用户可以使用的服务类型为HTTPS服务。
service-type https
缺省情况下,未配置用户的服务类型。
有关local-user、password、authorization-attribute和service-type命令的详细介绍,请参见“安全命令参考”中的“AAA”。
为了管理员定位gRPC问题的需要,可以开启gRPC日志功能,以便记录设备对gRPC报文的处理信息。
设备生成的gRPC日志信息会交给信息中心模块处理,信息中心模块的配置将决定日志信息的发送规则和发送方向。关于信息中心的详细描述请参见“网络管理和监控配置指导”中的“信息中心”。
如果gRPC操作频繁,设备会输出大量gRPC日志,这可能会影响设备性能,建议仅开启需关注的gRPC操作的日志功能。
(1) 进入系统视图。
system-view
(2) 开启gRPC Dial-in模式的日志功能。请至少选择其中一项进行配置。
¡ 开启gRPC Dial-in模式的RPC类操作日志功能。
grpc log dial-in rpc { all | { cli | get }* }
缺省情况下,gRPC Dial-in模式的RPC类操作日志功能处于关闭状态。
¡ 开启gRPC Dial-in模式的gNMI类操作日志功能。
grpc log dial-in gnmi { all | { capabilities | get | set | subscribe }* }
缺省情况下,gRPC Dial-in模式的gNMI Set操作日志功能处于开启状态,其他gNMI类操作日志功能处于关闭状态。
(1) 进入系统视图。
system-view
(2) 开启gRPC功能。
grpc enable
缺省情况下,gRPC功能处于关闭状态。
(3) (可选)配置gRPC使用的Telemetry数据模型。
grpc data-model { 2-layer | 3-layer }
缺省情况下,设备使用二层Telemetry数据模型上送数据。
设备使用二层Telemetry数据模型上送数据时,不支持对上送数据使用GPB编码格式。
设备通过传感器完成数据的采集,具体实现方式为配置采样的数据源,即采样路径。
传感器组是一到多个采样路径的集合。传感器组有两种类型:普通传感器组和gNMI模式的传感器组。
采样路径包括以下类型:
· 事件触发采样:传感器组的数据采样没有固定周期,仅由事件触发。关于事件触发采样类型的采样路径,请参见对应模块的《NETCONF XML API Event Reference》手册。
· 周期采样:传感器组以固定的时间间隔来进行数据采样。关于周期采样类型的采样路径,请参见对应模块的《NETCONF XML API Configuration Reference》和《NETCONF XML API Data Reference》手册。
· 条件触发采样:传感器组根据一定频率检测采样路径,如果满足推送条件,则采集数据并上送给采集器。关于条件触发采样类型的采样路径以及相关的检测频率、推送条件,请联系H3C技术支持。
条件触发采样类型的采样路径仅在gNMI模式的传感器组中配置才能生效。
建议同一传感器组内只配置一种类型的采样路径。
(1) 进入系统视图。
system-view
(2) 进入Telemetry视图。
telemetry
(3) 创建普通传感器组,并进入传感器组视图。
sensor-group group-name
(4) 配置采样路径。
sensor path path [ selection-nodes node-list ]
多次执行本命令可配置多个采样路径。多次执行本命令且指定的采样路径相同时,最后一次执行的命令生效。
(1) 进入系统视图。
system-view
(2) 进入Telemetry视图。
telemetry
(3) 创建gNMI模式传感器组,并进入传感器组视图。
sensor-group group-name gnmi
通过本命令进入已经创建的gNMI模式的传感器组视图时,不需要携带gnmi关键字。
(4) 配置采样路径。
sensor path path
多次执行本命令可配置多个采样路径。
采集器用于接收网络设备推送的采样数据。设备上需要建立目标组并在目标组中配置正确的采集器地址信息,才能和采集器通信。
建议系统中创建的目标组数量不超过5个,否则会影响系统性能。
往目标组添加采集器有两种方式:
· 指定IP地址方式
· 指定域名方式。使用该方式时需要注意:
¡ 通过域名指定采集器时,需要配置DNS(Domain Name System,域名系统),将采集器的域名解析为IPv4地址,订阅报文才能上送给采集器。关于域名解析的配置,请参见“三层技术-IP业务配置指导”中的“域名解析”。
¡ 可在设备上通过display dns host命令查看域名解析信息。如果一个域名对应多个IP地址,则DNS解析该域名得到的第一个路由可达的IP地址将作为订阅报文的上送地址。
用户可以在系统视图下通过grpc pki domain命令全局配置TLS加密(该加密方式使用PKI域中的证书),也可以在目标组视图下配置与指定采集器之间的TLS加密(该加密方式使用设备出厂自带的证书),且系统视图下的配置优先级较高。
如果采集器位于VPN中,请先配置VPN实例,再配置采集器。关于VPN实例的配置,请参见“MPLS配置指导”中的“MPLS L3VPN”。
(1) 进入系统视图。
system-view
(2) 进入Telemetry视图。
telemetry
(3) 创建目标组,并进入目标组视图。
destination-group group-name
(4) 配置采集器的IP地址和相关参数。
(IPv4网络)
ipv4-address ipv4-address [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
(IPv6网络)
ipv6-address ipv6-address [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
采集器的IPv6地址不能指定为IPv6链路本地地址。有关IPv6链路本地地址的介绍,请参见“三层技术-IP业务配置指导”中的“IPv6基础”。
多次执行本命令可配置多个采集器。配置本命令时,只要IP地址、端口号、VPN实例中任意一个属性不同,则被设备视为不同的采集器。
(5) 配置采集器的域名和相关参数。
(IPv4网络)
domain-name domain-name [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
(IPv6网络)
ipv6 domain-name domain-name [ port port-number ] [ vpn-instance vpn-instance-name ] [ tls ]
多次执行本命令可配置多个采集器。配置本命令时,只要域名、端口号、VPN实例中任意一个属性不同,则被设备视为不同的采集器。
完成传感器组和目标组的配置后,需要创建订阅并将二者关联,设备才能和目标组中的采集器建立gRPC连接,从而将订阅报文发送给采集器。
订阅分为普通订阅和gNMI模式的订阅。
普通订阅仅支持关联普通传感器组,gNMI模式的订阅仅支持关联gNMI模式的传感器组。
根据传感器组中的采样路径的类型,关联该传感器组的订阅的配置需要符合表1-2所示的条件,指定类型的采样路径才能生效。
传感器组的采样路径类型 |
订阅视图下是否配置push-mode condition-triggered |
订阅视图下sensor-group命令是否指定sample-interval参数 |
订阅视图下sensor-group命令是否允许指定suppress-time参数 |
周期采样 |
× |
√ |
× |
事件触发采样 |
× |
× |
× |
条件触发采样 |
√ |
× |
√ |
同一目标组不支持同时在普通订阅和gNMI模式订阅中使用。
(1) 进入系统视图。
system-view
(2) 进入Telemetry视图。
telemetry
(3) 创建普通订阅,并进入订阅视图。
subscription subscription-name
(4) (可选)配置设备发送的订阅报文的DSCP优先级。
dscp dscp-value
缺省情况下,设备发送的订阅报文的DSCP优先级为0。
DSCP优先级的取值越大,报文的优先级越高。
(5) (可选)配置设备发送订阅报文的源地址。
source-address { ipv4-address | interface interface-type interface-number | ipv6 ipv6-address }
缺省情况下,设备使用路由出接口的主IP地址作为发送订阅报文的源IP地址。
当设备发送订阅报文的源地址发生变化时,设备将会重新连接gRPC服务器。
(6) (可选)配置上送数据的编码格式。
encoding { gpb | json }
缺省情况下,上送数据的编码格式为JSON。
仅当设备使用三层Telemetry数据模型时,可以对上送数据使用GPB编码格式。
(7) (可选)配置订阅报文的JSON格式业务数据按行打时间戳。
json row-timestamp enable
缺省情况下,订阅报文的JSON格式业务数据按订阅报文打时间戳。
设备使用三层Telemetry数据模型时,不支持本命令。
(8) 配置关联传感器组。
sensor-group group-name [ sample-interval [ msec ] interval | suppress-time suppress-time ]
缺省情况下,未配置关联传感器组。
(9) 配置关联目标组。
destination-group group-name
缺省情况下,未配置关联目标组。
(1) 进入系统视图。
system-view
(2) 进入Telemetry视图。
telemetry
(3) 创建gNMI订阅,并进入订阅视图。
subscription subscription-name gnmi
通过本命令进入已经创建的gNMI模式的订阅视图时,不需要携带gnmi关键字。
(4) (可选)配置订阅的采样数据推送模式为条件触发推送模式。
push-mode condition-triggered
仅当订阅的传感器组中的采样路径类型为条件触发采样时,需要配置本命令。
缺省情况下,订阅仅支持推送来自周期采样和事件触发采样类型的采样路径的数据。
(5) (可选)配置设备发送的订阅报文的DSCP优先级。
dscp dscp-value
缺省情况下,设备发送的订阅报文的DSCP优先级为0。
DSCP优先级的取值越大,报文的优先级越高。
(6) (可选)配置设备发送订阅报文的源地址。
source-address { ipv4-address | interface interface-type interface-number | ipv6 ipv6-address }
缺省情况下,设备使用路由出接口的主IP地址作为发送订阅报文的源IP地址。
当设备发送订阅报文的源地址发生变化时,设备将会重新连接gRPC服务器。
(7) 配置关联传感器组。
sensor-group group-name [ sample-interval [ msec ] interval | suppress-time suppress-time ]
缺省情况下,未配置关联传感器组。
(8) 配置关联目标组。
destination-group group-name
缺省情况下,未配置关联目标组。
为了管理员定位gRPC问题的需要,可以开启gRPC日志功能,以便记录设备对gRPC报文的处理信息。
设备生成的gRPC日志信息会交给信息中心模块处理,信息中心模块的配置将决定日志信息的发送规则和发送方向。关于信息中心的详细描述请参见“网络管理和监控配置指导”中的“信息中心”。
gNMI模式的订阅不支持gRPC Dial-out模式的日志功能。
(1) 进入系统视图。
system-view
(2) 开启gRPC Dial-out模式的日志功能。
grpc log dial-out { all | { event | sample }* }
缺省情况下,gRPC Dial-out模式的日志功能处于关闭状态。
缺省情况下,设备和采集器建立的gRPC连接是非加密的。配置本功能引用PKI域后,设备和采集器会基于TLS协议进行通道加密和双向证书认证,从而提高gRPC通信的安全性。
指定的PKI域必须存在,并且PKI域中包含完整的证书和密钥。关于PKI的配置,请参见“安全配置指导”中的“PKI”。
指定PKI域后,gRPC功能将重启,与采集器的连接将短暂断开。Dial-in模式下,采集器需要重新发送连接请求才能继续访问;Dial-out模式下,设备会自动连接采集器。
(1) 进入系统视图。
system-view
(2) 配置设备和采集器建立gRPC连接时引用的PKI域。
grpc pki domain domain-name
缺省情况下,设备和采集器建立gRPC连接时不会引用PKI域。
gRPC功能在采样数据时可能会占用大量CPU资源,为避免影响其他业务运行,可以配置gRPC的CPU最大占用率,使gRPC的CPU占用率超过配置值时,设备中止采样,等CPU占用率降低后再继续采样。
(1) 进入系统视图。
system-view
(2) 配置gRPC的CPU最大占用率。
grpc cpu-usage max-percent percentage
缺省情况下,gRPC没有CPU占用率的限制。
在完成上述配置后,在任意视图下执行display命令可以显示配置后gRPC Dial-in模式的运行情况,通过查看显示信息验证配置的效果。
表1-3 gRPC显示和维护
操作 |
命令 |
显示gRPC的相关信息 |
display grpc [ verbose ] |
显示有最小采样周期的采样路径信息 |
display telemetry sensor-path |
本章节主要描述设备上的命令行配置。采集器上的gRPC对接软件需要另外开发,请参见“2.3 gRPC对接软件二次开发举例”。
如图1-3所示,设备作为gRPC服务器与采集器相连。采集器为gRPC客户端。
通过在设备上配置gRPC Dial-in模式,使gRPC客户端可以订阅设备上的LLDP事件。
图1-3 gRPC Dial-in模式配置组网图
在开始下面的配置之前,假设gRPC服务器与gRPC客户端的IP地址都已配置完毕,并且它们之间路由可达。
(1) 配置Device(gRPC服务器)
# 开启gRPC功能。
<Device> system-view
[Device] grpc enable
# 创建本地用户test,配置该用户的密码,授权用户角色为network-admin,可以使用的服务类型为HTTPS服务。
[Device] local-user test
[Device-luser-manage-test] password simple 123456TESTplat&!
[Device-luser-manage-test] authorization-attribute user-role network-admin
[Device-luser-manage-test] service-type https
[Device-luser-manage-test] quit
(2) 配置gRPC客户端
a. 在gRPC客户端安装gRPC环境,具体安装方式请参考相关文档。
b. 获取H3C提供的.proto文件(该文件中已写入订阅LLDP事件的配置),并通过protocol buffers编译器生成特定语言(例如Java、Python、C/C++、Go)的执行代码。
c. 编写客户端程序,调用上一步生成的代码。
d. 执行客户端程序,登录到gRPC服务器。
当设备发生LLDP事件时,gRPC客户端成功收到设备上的订阅信息。
订阅事件触发类型(一般带有“event”字符)的采样路径的传感器组时,请不要在sensor-group (subscription view)命令中指定周期采样参数sample-interval。
如图1-4所示,设备作为gRPC客户端与采集器相连。采集器为gRPC服务器,接收数据的端口号为50050。
通过在设备上配置gRPC Dial-out模式,使设备向采集器推送接口模块的事件数据。
图1-4 gRPC Dial-out模式配置组网图
在开始下面的配置之前,假设设备与采集器的IP地址都已配置完毕,并且它们之间路由可达。
# 开启gRPC功能。
<Device> system-view
[Device] grpc enable
# 创建传感器组Test,并添加采样路径为ifmgr/interfaceevent。
[Device] telemetry
[Device-telemetry] sensor-group Test
[Device-telemetry-sensor-group-Test] sensor path ifmgr/interfaceevent
[Device-telemetry-sensor-group-Test] quit
# 创建目标组collector1,并配置IP地址为192.168.2.1、端口号为50050的采集器。
[Device-telemetry] destination-group collector1
[Device-telemetry-destination-group-collector1] ipv4-address 192.168.2.1 port 50050
[Device-telemetry-destination-group-collector1] quit
# 创建订阅B,配置关联传感器组为Test,关联目标组为collector1。
[Device-telemetry] subscription B
[Device-telemetry-subscription-B] sensor-group Test
[Device-telemetry-subscription-B] destination-group collector1
[Device-telemetry-subscription-B] quit
当设备的接口模块上报事件后,采集器收到设备推送的事件数据。
如图1-5所示,设备作为gRPC客户端与采集器相连。采集器为gRPC服务器,接收数据的端口号为50050。
通过在设备上配置gRPC Dial-out模式,使设备以10秒的周期向采集器推送接口模块的设备能力信息。
图1-5 gRPC Dial-out模式配置组网图
在开始下面的配置之前,假设设备与采集器的IP地址都已配置完毕,并且它们之间路由可达。
# 开启gRPC功能。
<Device> system-view
[Device] grpc enable
# 创建传感器组test,并添加采样路径为ifmgr/devicecapabilities。
[Device] telemetry
[Device-telemetry] sensor-group test
[Device-telemetry-sensor-group-test] sensor path ifmgr/devicecapabilities
[Device-telemetry-sensor-group-test] quit
# 创建目标组collector1,并配置IP地址为192.168.2.1、端口号为50050的采集器。
[Device-telemetry] destination-group collector1
[Device-telemetry-destination-group-collector1] ipv4-address 192.168.2.1 port 50050
[Device-telemetry-destination-group-collector1] quit
# 创建订阅A,配置关联传感器组为test,数据采样和推送周期为10秒,关联目标组为collector1。
[Device-telemetry] subscription A
[Device-telemetry-subscription-A] sensor-group test sample-interval 10
[Device-telemetry-subscription-A] destination-group collector1
[Device-telemetry-subscription-A] quit
采集器每10秒收到一次设备推送的数据信息。
Protocol Buffers编码提供了一种灵活、高效、自动序列化结构数据的机制。Protocol Buffers与XML、JSON编码类似,不同之处在于Protocol Buffers是一种二进制编码,性能更高。
表2-1对比了Protocol Buffers和对应的JSON编码格式。
表2-1 Protocol Buffers和对应的JSON编码格式示例
Protocol Buffers编码 |
对应的JSON编码 |
{ 1:“H3C” 2:“H3C” 3:“H3C device_test” 4:“Syslog/LogBuffer” 5:"notification": { "Syslog": { "LogBuffer": { "BufferSize": 512, "BufferSizeLimit": 1024, "DroppedLogsCount": 0, "LogsCount": 100, "LogsCountPerSeverity": { "Alert": 0, "Critical": 1, "Debug": 0, "Emergency": 0, "Error": 3, "Informational": 80, "Notice": 15, "Warning": 1 }, "OverwrittenLogsCount": 0, "State": "enable" } }, "Timestamp": "1527206160022" } } |
{ "producerName": "H3C", "deviceName": "H3C", "deviceModel": "H3C device_test", "sensorPath": "Syslog/LogBuffer", "jsonData": { "notification": { "Syslog": { "LogBuffer": { "BufferSize": 512, "BufferSizeLimit": 1024, "DroppedLogsCount": 0, "LogsCount": 100, "LogsCountPerSeverity": { "Alert": 0, "Critical": 1, "Debug": 0, "Emergency": 0, "Error": 3, "Informational": 80, "Notice": 15, "Warning": 1 }, "OverwrittenLogsCount": 0, "State": "enable" } }, "Timestamp": "1527206160022" } } } |
Protocol Buffers编码通过proto文件描述数据结构,用户可以利用Protoc等工具软件根据proto文件自动生成其他编程语言(例如Java、C++)代码,然后基于这些生成的代码进行二次开发,以实现gRPC设备对接。
H3C为Dial-in模式和Dial-out模式分别提供了proto文件。
Dial-in模式的公共proto文件包括:
· grpc_service.proto:定义了Dial-in模式下的公共RPC方法。
· gnmi.proto:定义了gNMI类操作的公共RPC方法。
· gnmi_ext.proto:定义了gnmi.proto文件所需的扩展消息结构。
其中gnmi.proto和gnmi_ext.proto文件由Google发布,下载地址请参见“2.2.3 获取proto文件的方法”。
grpc_service.proto文件由H3C提供,其内容和含义如下:
syntax = "proto2";
package grpc_service;
message GetJsonReply { //Get方法应答结果
required string result = 1;
}
message SubscribeReply { //订阅结果
required string result = 1;
}
message ConfigReply { //配置结果
required string result = 1;
}
message ReportEvent { //订阅事件结果定义
required string token_id = 1; //登录token_id
required string stream_name = 2; //订阅的事件流名称
required string event_name = 3; //订阅的事件名
required string json_text = 4; //订阅结果json字符串
}
message GetReportRequest{ //获取事件订阅结果请求
required string token_id = 1; //登录成功后的token_id
}
message LoginRequest { //登录请求参数定义
required string user_name = 1; //登录请求用户名
required string password = 2; //登录请求密码
}
message LoginReply { //登录请求应答定义
required string token_id = 1; //登录成功后返回的token_id
}
message LogoutRequest { //退出登录请求参数定义
required string token_id = 1; //token_id
}
message LogoutReply { //退出登录返回结果定义
required string result = 1; //退出登录结果
}
message SubscribeRequest { //定义事件流名称
required string stream_name = 1;
}
message CliConfigArgs { //向设备下发配置命令,并指定命令行参数
required int64 ReqId = 1; //配置命令请求ID
required string cli = 2; //配置命令
}
message CliConfigReply { //设备返回配置命令行执行的结果
required int64 ResReqId = 1; //返回配置命令请求ID,与CliConfigArgs相对应
required string output = 2; //返回配置命令执行输出
required string errors = 3; //标记配置命令执行结果
}
message DisplayCmdArgs { //向设备下发display命令,并指定命令行参数
required int64 ReqId = 1; //display命令请求ID
required string cli = 2; //display命令
}
message DisplayCmdReply { //设备返回display命令行执行的结果
required int64 ResReqId =1; //display命令请求ID,与DisplayCmdArgs相对应
required string output = 2; //返回display命令执行输出
required string errors = 3; //标记display命令执行结果
}
service GrpcService { //定义gRPC方法
rpc Login (LoginRequest) returns (LoginReply) {} //登录方法
rpc Logout (LogoutRequest) returns (LogoutReply) {} //退出登录方法
rpc SubscribeByStreamName (SubscribeRequest) returns (SubscribeReply) {} //订阅事件流
rpc GetEventReport (GetReportRequest) returns (stream ReportEvent) {} //获取事件结果
rpc CliConfig (CliConfigArgs) returns (stream CliConfigReply) {} //gRPC支持通过命令行下发配置命令,并返回执行结果
rpc DisplayCmdTextOutput(DisplayCmdArgs) returns(stream DisplayCmdReply) {} //gRPC支持通过命令行下发display命令,并返回查询结果
}
Dial-in模式支持Device、Ifmgr、IPFW、LLDP、Syslog等业务模块proto文件。
以Device.proto文件为例,该文件定义了Device模块数据的RPC方法,其内容和含义如下:
syntax = "proto2";
import "grpc_service.proto";
package device;
message DeviceBase { //获取设备基本信息结构定义
optional string HostName = 1; //设备的名称
optional string HostOid = 2; //sysoid
optional uint32 MaxChassisNum = 3; //最大框数
optional uint32 MaxSlotNum = 4; //最大slot数
optional string HostDescription = 5; //设备描述信息
}
message DevicePhysicalEntities { //设备物理实体信息
message Entity {
optional uint32 PhysicalIndex = 1; //实体索引
optional string VendorType = 2; //vendor类型
optional uint32 EntityClass = 3;//实体类型
optional string SoftwareRev = 4; //软件版本
optional string SerialNumber = 5; //序列号
optional string Model = 6; //模式
}
repeated Entity entity = 1;
}
service DeviceService { //定义的RPC方法
rpc GetJsonDeviceBase(DeviceBase) returns (grpc_service.GetJsonReply) {} //获取设备基本信息
rpc GetJsonDevicePhysicalEntities(DevicePhysicalEntities) returns (grpc_service.GetJsonReply) {} //获取设备实体信息
}
Dial-out模式的公共proto文件包括:
· grpc_dialout.proto:定义了Dial-out模式下普通订阅的公共RPC方法。
· grpc_dialout_v3.proto文件:定义了Dial-out模式下三层Telemetry数据模型的公共RPC方法。
· telemetry.proto文件定义了Dial-out模式下三层Telemetry数据模型的数据采样参数。
· dial_out.proto:定义了Dial-out模式下gNMI模式订阅的报文格式。
· gnmi.proto:定义了gNMI模式订阅的公共RPC方法。
· gnmi_ext.proto:定义了gnmi.proto文件所需的扩展消息结构。
其中dial_out.proto文件由SONIC发布,gnmi.proto和gnmi_ext.proto文件由Google发布,下载地址请参见“2.2.3 获取proto文件的方法”。
grpc_dialout.proto文件由H3C提供,其内容和含义如下:
syntax = "proto2";
package grpc_dialout;
message DeviceInfo{ //推送的设备信息
required string producerName = 1; //厂商名
required string deviceName = 2; //设备的名称
required string deviceModel = 3; //设备型号
}
message DialoutMsg{ //推送的消息格式描述
required DeviceInfo deviceMsg = 1; //DeviceInfo所描述的设备信息
required string sensorPath = 2; //采样路径,对应netconf表的xpath路径
required string jsonData = 3; //采样结果数据(JSON格式字符串)
}
message DialoutResponse{ //采集器(gRPC服务器)返回信息,预留(暂不处理返回值,可填充任意值)
required string response = 1;
}
service GRPCDialout { //推送方法
rpc Dialout(stream DialoutMsg) returns (DialoutResponse);
}
# grpc_dialout_v3.proto文件的内容和含义如下:
syntax = "proto3";
package grpc_dialout_v3;
message DialoutV3Args{
int64 ReqId = 1;//请求ID
bytes data = 2;//承载的数据
string errors = 3;//产生错误时的描述信息
int32 totalSize = 4;//分片时信息的总大小,未分片时为0
}
service gRPCDialoutV3{
rpc DialoutV3(stream DialoutV3Args) returns (stream DialoutV3Args) {};
}
# telemetry.proto文件的内容和含义如下:
syntax = "proto3";
package telemetry;
message Telemetry {
string producer_name = 1;//厂商名
string node_id_str = 2;//设备名
string product_name = 3;//产品名
string subscription_id_str = 15;//订阅名
string sensor_path = 16;//采样路径
uint64 collection_id = 17;//标识采样轮次
uint64 collection_start_time = 18;//采样开始时间
uint64 msg_timestamp = 19;//生成本消息的时间戳
uint64 collection_end_time = 20;//采样结束时间
uint32 current_period = 21;//采样精度
string except_desc = 22;//异常描述信息
enum Encoding {
Encoding_JSON = 0;//表示GPB数据编码格式
Encoding_GPB = 1;//表示JSON数据编码格式
};
Encoding encoding = 23;//数据编码格式
string data_str = 24;// 数据编码非GPB时有效,否则为空
TelemetryGPBTable data_gpb = 25;//承载的数据由TelemetryGPBTable定义
}
message TelemetryGPBTable {
repeated TelemetryRowGPB row = 1;//数组定义,标识数据是TelemetryRowGPB结构的重复
}
message TelemetryRowGPB {
uint64 timestamp = 1;//采样当前实例的时间戳
bytes keys = 10;//保留字段
bytes content = 11;//承载的采样实例数据
}
业务数据使用GPB格式编码时,需要配合对应的业务模块proto文件才能解码。
Dial-out模式支持Device、Ifmgr等业务模块proto文件。
可联系H3C技术支持提供proto文件。
gnmi.proto和gnmi_ext.proto文件可从以下网站下载:
· https://github.com/openconfig/gnmi/tree/master/proto/gnmi/gnmi.proto
· https://github.com/openconfig/gnmi/tree/master/proto/gnmi_ext/gnmi_ext.proto
dial-out.proto文件可从以下网站下载:
· https://github.com/Azure/sonic-telemetry/blob/master/proto/dial_out.proto
本举例开发的软件用于实现:
· 采集器获取设备数据(Dial-in模式下的Get操作、gNMI Capabilities操作、gNMI Get操作、gNMI Subscribe操作或Dial-out模式)
· 采集器向设备下发配置(Dial-in模式下的gNMI Set操作或CLI操作)
开发环境为Linux,编程语言以C++为例。
(1) 获取proto文件:
¡ Dial-in模式下的Get操作:需要grpc_service.proto文件和具体业务模块对应的proto文件。
¡ Dial-in模式下的gNMI类操作:需要grpc_service.proto、gnmi.proto和gnmi_ext.proto文件。
¡ Dial-in模式下的CLI操作:需要grpc_service.proto文件。
¡ Dial-out模式下的普通订阅:需要grpc_dialout.proto文件。
¡ Dial-out模式下的gNMI模式订阅:需要dial_out.proto、gnmi.proto和gnmi_ext.proto文件。
(2) 获取处理proto文件的工具软件protoc。
下载地址:https://github.com/google/protobuf/releases
(3) 获取对应开发语言的protobuf插件,例如C++插件protobuf-cpp。
下载地址:https://github.com/google/protobuf/releases
生成proto文件对应的C++代码。
将需要的proto文件收集到当前目录下,例如grpc_service.proto和BufferMonitor.proto。
$protoc --plugin=./grpc_cpp_plugin --grpc_out=. --cpp_out=. *.proto
将grpc_dialout.proto文件收集到当前目录下。
$ protoc --plugin=./grpc_cpp_plugin --grpc_out=. --cpp_out=. *.proto
对于Dial-in模式,主要是实现gRPC客户端代码(采集器上使用)。
由于生成的proto代码已经封装好了对应的服务类,只要在编写的客户端代码中调用其RPC方法,客户端就能够向设备(gRPC服务器)发起对应的RPC请求。
客户端代码主要包括以下3部分:
· 进行登录操作,获取token_id。
· 为要发起的RPC方法准备参数,用proto生成的服务类发起RPC调用并解析返回结果。
· 退出登录。
以调用GrpcService和BufferMonitorService服务类为例,编码步骤如下:
(1) 编写一个GrpcServiceTest类。
在这个类中使用由grpc_service.proto生成的GrpcService::Stub类,通过grpc_service.proto自动生成的Login和Logout方法分别完成登录和退出。
class GrpcServiceTest
{
public:
/* 构造函数 */
GrpcServiceTest(std::shared_ptr<Channel> channel): GrpcServiceStub(GrpcService::NewStub(channel)) {}
/* 成员函数 */
int Login(const std::string& username, const std::string& password);
void Logout();
void listen();
Status listen(const std::string& command);
/* 成员变量 */
std::string token;
private:
std::unique_ptr<GrpcService::Stub> GrpcServiceStub; //使用grpc_service.proto生成的GrpcService::Stub类
};
(2) 实现自定义的Login方法。
通过用户输入的用户名,密码调用GrpcService::Stub类的Login方法完成登录。
int GrpcServiceTest::Login(const std::string& username, const std::string& password)
{
LoginRequest request; //设置用户名密码
request.set_user_name(username);
request.set_password(password);
LoginReply reply;
ClientContext context;
//调用登录方法
Status status = GrpcServiceStub->Login(&context, request, &reply);
if (status.ok())
{
std::cout << "login ok!" << std::endl;
std::cout <<"token id is :" << reply.token_id() << std::endl;
token = reply.token_id(); //登录成功,获取到token.
return 0;
}
else{
std::cout << status.error_code() << ": " << status.error_message()
<< ". Login failed!" << std::endl;
return -1;
}
}
(3) 发起对设备的RPC方法请求。
这里以订阅接口丢包事件举例:
rpc SubscribePortQueDropEvent(PortQueDropEvent) returns (grpc_service.SubscribeReply) {}
(4) 编写一个BufMon_GrpcClient类来封装发起的RPC方法。
使用BufferMonitor.proto自动生成的BufferMonitorService::Stub类完成RPC方法的调用。
class BufMon_GrpcClient
{
public:
BufMon_GrpcClient(std::shared_ptr<Channel> channel): mStub(BufferMonitorService::NewStub(channel))
{}
std::string BufMon_Sub_AllEvent(std::string token);
std::string BufMon_Sub_BoardEvent(std::string token);
std::string BufMon_Sub_PortOverrunEvent(std::string token);
std::string BufMon_Sub_PortDropEvent(std::string token);
/* get 表项 */
std::string BufMon_Sub_GetStatistics(std::string token);
std::string BufMon_Sub_GetGlobalCfg(std::string token);
std::string BufMon_Sub_GetBoardCfg(std::string token);
std::string BufMon_Sub_GetNodeQueCfg(std::string token);
std::string BufMon_Sub_GetPortQueCfg(std::string token);
private:
std::unique_ptr<BufferMonitorService::Stub> mStub; //使用BufferMonitor.proto自动生成的类
};
(5) 实现自定义的std::string BufMon_Sub_PortDropEvent(std::string token)方法完成接口丢包事件订阅。
std::string BufMon_GrpcClient::BufMon_Sub_PortDropEvent(std::string token)
{
std::cout << "-------BufMon_Sub_PortDropEvent-------- " << std::endl;
PortQueDropEvent stNodeEvent;
PortQueDropEvent_PortQueDrop* pstParam = stNodeEvent.add_portquedrop();
UINT uiIfIndex = 0;
UINT uiQueIdx = 0;
UINT uiAlarmType = 0;
std::cout<<"Please input interface queue info : ifIndex queIdx alarmtype " << std::endl;
cout<<"alarmtype : 1 for ingress; 2 for egress; 3 for port headroom"<<endl;
std::cin>>uiIfIndex>>uiQueIdx>>uiAlarmType; //设置订阅参数,接口索引等。
pstParam->set_ifindex(uiIfIndex);
pstParam->set_queindex(uiQueIdx);
pstParam->set_alarmtype(uiAlarmType);
ClientContext context;
/* token need add to context */ //设置登录成功后返回的token_id
std::string key = "token_id";
std::string value = token;
context.AddMetadata(key, value);
SubscribeReply reply;
Status status = mStub->SubscribePortQueDropEvent(&context,stNodeEvent,&reply); //调用RPC方法
return reply.result();
}
(6) 循环等待事件上报。
在之前的GrpcServiceTest类中实现此方法,代码如下:
void GrpcServiceTest::listen()
{
GetReportRequest reportRequest;
ClientContext context;
ReportEvent reportedEvent;
/* add token to request */
reportRequest.set_token_id(token);
std::unique_ptr< ClientReader< ReportEvent>> reader(GrpcServiceStub->GetEventReport(&context, reportRequest)); //通过grpc_service.proto自动生成的类的GetEventReport来获取事件信息
std::string streamName;
std::string eventName;
std::string jsonText;
std::string token;
JsonFormatTool jsonTool;
std::cout << "Listen to server for Event" << std::endl;
while(reader->Read(&reportedEvent) ) //读取收到的上报事件
{
streamName = reportedEvent.stream_name();
eventName = reportedEvent.event_name();
jsonText = reportedEvent.json_text();
token = reportedEvent.token_id();
std::cout << "/***********EVENT COME**************/" << std::endl;
std::cout << "TOKEN: " << token << std::endl;
std::cout << "StreamName: "<< streamName << std::endl;
std::cout << "EventName: " << eventName << std::endl;
std::cout << "JsonText without format: " << std::endl << jsonText << std::endl;
std::cout << std::endl;
std::cout << "JsonText Formated: " << jsonTool.formatJson(jsonText) << std::endl;
std::cout << std::endl;
}
Status status = reader->Finish();
std::cout << "Status Message:" << status.error_message() << "ERROR code :" << status.error_code();
}
(7) 这样就完成了Dial-in模式的登录和RPC请求调用,最后调用Logout退出即可。
void GrpcServiceTest:: Logout ()
{
LogoutRequest request;
request.set_token_id(token);
LogoutReply reply;
ClientContext context;
Status status = mStub->Logout(&context, request, &reply);
std::cout << "Logout! :" << reply.result() << std::endl;
}
编码步骤如下:
(1) 编写一个GrpcServiceTest类。
与Get操作相同。
(2) 实现自定义的Login方法。
与Get操作相同。
(3) 发起对设备的RPC方法请求。
rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);
(4) 编写gnmi_client类来封装发起的RPC方法。
class gnmi_client
{
public:
explicit gnmi_client(const std::string &address,const std::string &tokenId);
bool TestCapabilities();
bool TestGet();
bool TestSet();
bool TestSubscribePool();
bool TestSubscribeOnce();
bool TestSubscribeStream();
bool TestSubscribeStreamWithAlias();
private:
void PrintCapabilityResponse(const gnmi::CapabilityResponse &response);
void PrintGetResponse(const gnmi::GetResponse &response);
void PrintSubscribeResponse(const gnmi::SubscribeResponse &response);
void PrintSubscribeRequest(const gnmi::SubscribeRequest &request);
void PrintGetRequest(const gnmi::GetRequest &request);
void PrintSetRequest(const gnmi::SetRequest &request);
void FillGetRequest(gnmi::GetRequest &request);
void FillSetRequest(gnmi::SetRequest &request);
void FillSubscribeRequestByOnce(gnmi::SubscribeRequest &request);
void FillSubscribeRequestByPool(gnmi::SubscribeRequest &request);
void FillSubscribeRequestByStream(gnmi::SubscribeRequest &request);
void FillSubscribePool(gnmi::SubscribeRequest &request);
void FillSubscribeAlias(gnmi::SubscribeRequest &request);
private:
std::unique_ptr<gnmi::gNMI::Stub> mStubGnmiService;
std::string mTokenID;
};
(5) 实现自定义的TestCapabilities ()方法,以获取Capabilities内容。
bool gnmi_client::TestCapabilities()
{
CapabilityRequest request;
CapabilityResponse response;
ClientContext context;
context.AddMetadata("token_id", mTokenID);
std::cout << std::endl << "CapabilitiesRequest => " << std::endl;
Status ret = mStubGnmiService->Capabilities(&context,request,&response);
if( StatusCode::OK != ret.error_code() )
{
cout << "TestCapabilities ErrorMessage:" << ret.error_message() << endl;
return false;
}
else
{
PrintCapabilityResponse(response);
return true;
}
}
(6) 调用Logout退出登录。
与Get操作相同。
编码步骤如下:
(1) 编写一个GrpcServiceTest类。
与Get操作相同。
(2) 实现自定义的Login方法。
与Get操作相同。
(3) 发起对设备的RPC方法请求。
rpc Get(GetRequest) returns (GetResponse);
(4) 编写gnmi_client类来封装发起的RPC方法。
与gNMI Capabilities操作相同。
(5) 以获取Device/Base/HostName内容为例,实现自定义的TestGet方法。
bool gnmi_client::TestGet()
{
GetRequest request;
FillGetRequest(request);
PrintGetRequest(request);
GetResponse response;
ClientContext context;
context.AddMetadata("token_id", mTokenID);
Status ret = mStubGnmiService->Get(&context,request,&response);
if( StatusCode::OK != ret.error_code() )
{
cout << "TestGet ErrorMessage:" << ret.error_message() << endl;
return false;
}
else
{
PrintGetResponse(response);
return true;
}
}
void gnmi_client::FillGetRequest(gnmi::GetRequest &request)
{
auto prefix = request.mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("Device");
auto path1 = request.add_path();
auto pathelem02 = path1->add_elem();
pathelem02->set_name("Base");
auto pathelem03 = path1->add_elem();
pathelem03->set_name("HostName");
}
(6) 调用Logout退出登录。
与Get操作相同。
(1) 编写一个GrpcServiceTest类。
与Get操作相同。
(2) 实现自定义的Login方法。
与Get操作相同。
(3) 发起对设备的RPC方法请求。
这里以Device模块为例,实现Set方法:
rpc Set(SetRequest) returns (SetResponse)
(4) 编写gNMITest类来封装发起的RPC方法。
使用gnmi.proto自动生成的gNMI::Stub类完成对RPC方法的调用。
class gNMITest
{
public:
gNMITest(std::shared_ptr<Channel> channel, const std::string tokenId ):
mStubGrpcService(GrpcService::NewStub(channel)),
mStubgNMIService(gNMI::NewStub(channel)),
mTokenID(tokenId ){}
SetResponse TestSetResponseInformation(SetRequest &request, const std::string tokenId);
/*---delete: Device/Base/HostName,将HostName恢复缺省值-----------*/
SetResponse DeleteDeviceBaseHostName(); /* delete: Device Base/HostName*/
/* update: Device/Base/HostName, string_val("string_hostname") */
SetResponse UpdateDeviceBaseHostNameStringVal();
/* replace: Device/Base/HostName, string_val("string_hostname") */
REPL_001 SetResponse ReplaceDeviceBaseHostNameStringVal();
private:
std::unique_ptr<GrpcService::Stub> mStubGrpcService;
std::unique_ptr<gNMI::Stub> mStubgNMIService;
std::string mTokenID;
};
(5) 实现自定义的方法以完成对业务模块数据(以Device为例)的Set操作。
// 调用Set方法,实现client与server通信,并返回response。
SetResponse gNMITest::TestSetResponseInformation(SetRequest &request, const std::string tokenId)
{
SetResponse reply;
ClientContext context;
context.AddMetadata("token_id", tokenId);
/* 调用Set方法 */
Status ret = mStubgNMIService->Set(&context,request,&reply);
if( StatusCode::OK != ret.error_code())
{
std::cout<<"error: "<<ret.error_message()<<std::endl;
}
return reply;
}
// Delete操作
SetResponse gNMITest:: DeleteDeviceBaseHostName () /* prefix == Device/Base */
{
SetRequest request;
/* SetRequest->prefix */
Path *path01 = request.mutable_prefix();
PathElem *pathelem01 = path01->add_elem();
pathelem01->set_name("Device");
PathElem *pathelem02 = path01->add_elem();
pathelem02->set_name("Base");
/* SetRequest->delete */
Path *path02 = request.add_delete_();
PathElem *pathelem03 = path02->add_elem();
pathelem03->set_name("HostName");
/* gather response info. */
return TestSetResponseInformation(request, mTokenID);
}
// Update操作
SetResponse gNMITest::UpdateDeviceBaseHostNameStringVal()
{
SetRequest request;
/* SetRequest->prefix */
Path *path01 = request.mutable_prefix();
PathElem *pathelem01 = path01->add_elem();
pathelem01->set_name("Device");
PathElem *pathelem02 = path01->add_elem();
pathelem02->set_name("Base");
/* SetRequest->update */
Update *update01 = request.add_update();
Path *path02 = update01->mutable_path();
PathElem *pathelem03 = path02->add_elem();
pathelem03->set_name("HostName");
TypedValue *typevalue01 = update01->mutable_val();
typevalue01->set_string_val("string_hostname");
/* gather response info. */
return TestSetResponseInformation(request, mTokenID);
}
// Replace操作
SetResponse gNMITest::ReplaceDeviceBaseHostNameStringVal()
{
SetRequest request;
/* SetRequest->prefix */
Path *path01 = request.mutable_prefix();
PathElem *pathelem01 = path01->add_elem();
pathelem01->set_name("Device");
PathElem *pathelem02 = path01->add_elem();
pathelem02->set_name("Base");
/* SetRequest->replace */
Update *replace01 = request.add_replace();
Path *path02 = replace01->mutable_path();
PathElem *pathelem03 = path02->add_elem();
pathelem03->set_name("HostName");
TypedValue *typevalue01 = replace01->mutable_val();
typevalue01->set_string_val("string_hostname");
/* gather response info. */
return TestSetResponseInformation(request, mTokenID);
}
(6) 调用Logout退出登录。
与Get操作相同。
编码步骤如下:
(1) 编写一个GrpcServiceTest类。
与Get操作相同。
(2) 实现自定义的Login方法。
与Get操作相同。
(3) 发起对设备的RPC方法请求。
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);
(4) 编写gnmi_client类来封装发起的RPC方法。
与gNMI Capabilities操作相同。
(5) 实现自定义的订阅方法。
void gnmi_client::FillSubscribeRequestByOnce(gnmi::SubscribeRequest &request)
{
auto subscribeList = request.mutable_subscribe();
auto prefix = subscribeList->mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("LLDP");
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path();
auto pathelem02 = path->add_elem();
pathelem02->set_name("NeighborEvent");
auto pathelem03 = path->add_elem();
pathelem03->set_name("Neighbor");
(*pathelem03->mutable_key())["IfName"] = "xxx";
subscribeList->set_mode(::gnmi::SubscriptionList_Mode_ONCE);
subscribeList->set_encoding(::gnmi::JSON);
}
void gnmi_client::FillSubscribeRequestByPool(gnmi::SubscribeRequest &request)
{
auto subscribeList = request.mutable_subscribe();
auto prefix = subscribeList->mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("Device");
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path();
auto pathelem02 = path->add_elem();
pathelem02->set_name("CPUs");
auto pathelem03 = path->add_elem();
pathelem03->set_name("CPU");
auto pathelem04 = path->add_elem();
pathelem04->set_name("CPUUsage");
subscribeList->set_mode(::gnmi::SubscriptionList_Mode_POLL);
subscribeList->set_encoding(::gnmi::JSON);
}
void gnmi_client::FillSubscribeRequestByStream(gnmi::SubscribeRequest &request)
{
auto subscribeList = request.mutable_subscribe();
auto prefix = subscribeList->mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("Diagnostic");
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path();
auto pathelem02 = path->add_elem();
pathelem02->set_name("CPUEvent");
auto pathelem03 = path->add_elem();
pathelem03->set_name("CPU");
(*pathelem03->mutable_key())["Chassis#condition"] = "equal:1";
subscribe->set_mode(::gnmi::ON_CHANGE);
subscribe->set_sample_interval(1000);
subscribe->set_suppress_redundant(false);
subscribe->set_heartbeat_interval(1000);
subscribeList->set_mode(::gnmi::SubscriptionList_Mode_STREAM);
subscribeList->set_encoding(::gnmi::JSON);
}
void gnmi_client::FillSubscribeAlias(gnmi::SubscribeRequest &request)
{
auto aliases = request.mutable_aliases();
auto alias = aliases->add_alias();
auto path = alias->mutable_path();
auto pathelem01 = path->add_elem();
pathelem01->set_name("Device");
auto pathelem02 = path->add_elem();
pathelem02->set_name("CPUs");
auto pathelem03 = path->add_elem();
pathelem03->set_name("CPU");
auto pathelem04 = path->add_elem();
pathelem04->set_name("CPUUsage");
alias->set_alias("#cpu_usage");
}
(6) 调用Logout退出登录。
与Get操作相同。
(1) 编写一个GrpcServiceTest类。
与Get操作相同。
(2) 实现自定义的Login方法。
与Get操作相同。
(3) 发起对设备的RPC方法请求。
这里使用grpc_service.proto文件中的CliConfig方法:
rpc CliConfig (CliConfigArgs) returns (stream CliConfigReply) {}
(4) 使用GrpcServiceTest类封装发起的RPC请求。
见Get操作中的GrpcServiceTest类。
(5) 实现自定义的方法以支持CliConfig操作。
// make a thread to listen the sever and get message
Status GrpcServiceTest::listen(const std::string& command)
{
CliConfigArgs reportRequest;
ClientContext context;
CliConfigReply reportedEvent;
std::string key = "token_id";
std::string value = token;
context.AddMetadata(key, value);
/* add token to request */
reportRequest.set_reqid(12345678);
reportRequest.set_cli(command);
std::unique_ptr< ClientReader< CliConfigReply>> reader(mStub->CliConfig(&context, reportRequest));
std::string streamName;
std::string output;
int64 resreqid;
std::cout << "Command result" << std::endl;
while( reader->Read(&reportedEvent) )
{
streamName = reportedEvent.errors();
output = reportedEvent.output();
resreqid = reportedEvent.resreqid();
std::cout << "resreqid: "<< resreqid << std::endl;
std::cout << "errors: "<< streamName << std::endl;
std::cout << "output: \n"<< output << "\n"<< std::endl;
}
Status status = reader->Finish();
return status;
}
(6) 循环等待下发命令。
在main函数中实现,并在下发命令之后返回命令执行结果(在GrpcServiceTest类中实现),main函数代码如下:
int main(int argc, char *argv[])
{
const char *cmd;
unsigned int i = 0;
unsigned int cycle = 0;
if (4 == argc)
{
g_server_address = argv[1];
g_username = argv[2];
g_password = argv[3];
std::cout << "server_address: " << g_server_address <<std::endl;
std::cout << "username: " << g_username << " " << "password: " << g_password << std::endl;
auto channel = grpc::CreateChannel(g_server_address,grpc::InsecureChannelCredentials());
// 1. login
GrpcServiceTest reporter(channel);
if(0 != reporter.Login(g_username, g_password))
{
return 0;
}
while(1)
{// 2.循环读取命令并执行
std::cout<<"\n\nPlease Input Command:\n";
getline(std::cin,g_command); // 获取下发的命令
Status status = reporter.listen(g_command);
if (!status.ok())
{
std::cout << status.error_code() << ": " << status.error_message() << std::endl;
break;
}
}
}
std::cout<<"Complete exec Command."<<std::endl;
return 0;
}
(7) 调用Logout退出登录。
与Get操作相同。
对于Dial-out模式,主要是实现服务端代码,使采集器(gRPC服务器)接收从设备上获取到的数据并进行解析。
服务端代码主要包括以下2部分:
· 继承自动生成的GRPCDialout::Service类,重载自动生成的RPC服务Dialout,并完成解析,获得相应字段内容。
· 将RPC服务注册到指定监听端口上。
编码步骤如下:
(1) 继承并重载RPC服务Dialout。
新建一个类DialoutTest并继承GRPCDialout::Service。
class DialoutTest final : public GRPCDialout::Service { //重载自动生成的抽象类
Status Dialout(ServerContext* context, ServerReader< DialoutMsg>* reader, DialoutResponse* response) override; //实现Dialout RPC方法
};
(2) 将DialoutTest服务注册为gRPC服务,并指定监听端口。
using grpc::Server;
using grpc::ServerBuilder;
std::string server_address("0.0.0.0:60057"); //指定要监听的地址和端口
DialoutTest dialout_test; //定义(1)中声明的对象
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());//添加监听
builder.RegisterService(&dialout_test); //注册服务
std::unique_ptr<Server> server(builder.BuildAndStart()); //启动服务
server->Wait();
(3) 实现Dialout方法,实现数据解析。
Status DialoutTest::Dialout(ServerContext* context, ServerReader< DialoutMsg>* reader, DialoutResponse* response)
{
DialoutMsg msg;
while( reader->Read(&msg))
{
const DeviceInfo &device_msg = msg.devicemsg();
std::cout<< "Producer-Name: " << device_msg.producername() << std::endl;
std::cout<< "Device-Name: " << device_msg.devicename() << std::endl;
std::cout<< "Device-Model: " << device_msg.devicemodel() << std::endl;
std::cout<<"Sensor-Path: " << msg.sensorpath()<<std::endl;
std::cout<<"Json-Data: " << msg.jsondata()<<std::endl;
std::cout<<std::endl;
}
response->set_response("test");
return Status::OK;
}
(4) 通过Read方法获取到proto文件生成的DialoutMsg对象后,可以调用对应的方法获取相应的字段值。
gNMI类操作中添加列的方法如表2-2所示。
表2-2 gNMI类操作中添加列的方法
请求描述 |
请求示例 |
客户端代码示例 |
对key中的列赋值 |
path: |
auto pathelem01 = path->add_elem(); |
对key中的列属性赋值 |
path: |
auto pathelem01 = path->add_elem(); |
对key中的分组的列赋值 |
path: |
auto pathelem01 = path->add_elem(); |
对key中的分组的列的属性赋值 |
path: |
auto pathelem01 = path->add_elem(); |
通过key对列属性赋值 |
path: |
auto pathelem01 = path->add_elem(); |
对于JSON中的列和属性赋值 |
{ |
{ |
不同YANG文件中存在同名的顶层节点时,可以在顶层的同名节点名之前加上YANG模型名来精确指定该节点;如果不加YANG模型名,则自动匹配第一个找到的同名节点 例如: 在节点interfaces前加上YANG模型名oc-if,表示指定OpenConfig组织发布的YANG文件中的interfaces节点 |
path: |
auto pathelem01 = path->add_elem(); |
gNMI Get操作支持的属性如下:
· regExp(正则表达式)
当过滤条件比较复杂时,可以在指定列上设置regExp属性为一个正则表达式,以完成过滤的目的。如下示例用于获取接口的描述信息,并要求这些描述信息全部为大写字母,不能有其他字符。
auto prefix = request.mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("Ifmgr");
auto path1 = request.add_path();
auto pathelem02 = path1->add_elem();
pathelem02->set_name("Interfaces");
auto pathelem03 = path1->add_elem();
pathelem03->set_name("Interface");
auto pathelem04 = path1->add_elem();
pathelem04->set_name("Description");
(*pathelem04->mutable_key())["#regExp"] = "^[A-Z]*$";
· match(条件匹配)
条件匹配命令如表2-3所示。
操作 |
命令 |
说明 |
大于 |
match="more:value" |
值大于value,支持的数据类型为:日期、数字、字符串 |
小于 |
match="less:value" |
值小于value,支持的数据类型为:日期、数字、字符串 |
不小于 |
match="notLess:value" |
值不小于value,支持的数据类型为:日期、数字、字符串 |
不大于 |
match="notMore:value" |
值不大于value,支持的数据类型为:日期、数字、字符串 |
等于 |
match="equal:value" |
值等于value,支持的数据类型为:日期、数字、字符串、OID、BOOL |
不等于 |
match="notEqual:value" |
值不等于value,支持的数据类型为:日期、数字、字符串、OID、BOOL |
包含 |
match="include:string" |
包含字符串string,支持的数据类型为:字符串 |
不包含 |
match="exclude:string" |
不能包含字符串string,支持的数据类型为:字符串 |
开始于 |
match="startWith:string" |
以字符串string开头,支持的数据类型为:字符串、OID |
结束于 |
match="endWith:string" |
以字符串string结束,支持的数据类型为:字符串 |
如下示例用于获取实体扩展信息中CPU利用率大于50%的实体。
auto prefix = request.mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("Device");
auto path1 = request.add_path();
auto pathelem02 = path1->add_elem();
pathelem02->set_name("ExtPhysicalEntities");
auto pathelem03 = path1->add_elem();
pathelem03->set_name("Entity");
auto pathelem04 = path1->add_elem();
pathelem04->set_name("CpuUsage");
(*pathelem04->mutable_key())["#match"] = "more:50";
· valuetype(属性值类型)
获取接口数据时,如果IfIndex、vrfindex列的值为数字,设备无法识别该值为名称类型还是索引类型。此时,用户可以使用valuetype指定该值的类型。valuetype取值为:
属性 |
说明 |
name |
值为名称类型 |
index |
值为索引类型 |
auto |
设备先按名称类型进行匹配,如果没有匹配到任何信息,再按照索引类型进行匹配 如果不指定valuetype的属性,缺省使用auto |
如下示例中,IfIndex属性值为1,属性值类型为index:
auto prefix = request.mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("VLAN");
auto path1 = request.add_path();
auto pathelem02 = path1->add_elem();
pathelem02->set_name("TrunkInterfaces");
auto pathelem03 = path1->add_elem();
pathelem03->set_name("Interface");
(*pathelem03->mutable_key())["IfIndex"] = "1";
(*pathelem03->mutable_key())["IfIndex#valuetype"] = "index";
· count
该属性用于获取指定数量的数据项,例如运行状态、运行配置信息。
如下为一个携带了count属性的示例:
auto prefix = request.mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("Syslog");
auto path1 = request.add_path();
auto pathelem02 = path1->add_elem();
pathelem02->set_name("Logs");
(*pathelem03->mutable_key())["count"] = "5";
auto pathelem03 = path1->add_elem();
pathelem03->set_name("Log");
(*pathelem03->mutable_key())["Index"] = "10";
· 值过滤
在Get操作中,对表中的列直接指定值,设备将对这些值进行严格匹配。如果指定了多个列的值,则返回同时符合这几个条件的数据。
如下为匹配IfIndex=1的示例:
auto prefix = request.mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("VLAN");
auto path1 = request.add_path();
auto pathelem02 = path1->add_elem();
pathelem02->set_name("TrunkInterfaces");
auto pathelem03 = path1->add_elem();
pathelem03->set_name("Interface");
(*pathelem03->mutable_key())["IfIndex"] = "1";
gNMI Set支持的属性如下:
· incremental
该属性作用于集合性质的列,例如vlan permitlist列表。XML请求中有增量下发选项时,最终执行结果不影响本列原有的数据。
例如,下发一个接口的VLAN配置,使用增量下发,262接口原有Untagged VLAN列表为12~15,下发后为1~10,12~15。请求如下:
Path *path01 = request.mutable_prefix();
PathElem *pathelem01 = path01->add_elem();
pathelem01->set_name("VLAN");
PathElem *pathelem02 = path01->add_elem();
pathelem02->set_name("HybridInterfaces");
Update *Update01 = request.add_update();
Path *path02 = Update01->mutable_path();
PathElem *pathelem03 = path02->add_elem();
pathelem03->set_name("Interface");
(*pathelem03->mutable_key())["IfName"] = "262";
(*pathelem03->mutable_key())["UntaggedVlanList#incremental"] = "true";
(*pathelem03->mutable_key())["UntaggedVlanList"] = "1-10";
· valuetype
配置接口数据时,如果IfIndex和vrfindex列的值为数字,设备无法识别该值为名称类型还是索引类型。此时,用户可以使用valuetype指定该值的类型。valuetype取值为:
属性 |
说明 |
name |
值为名称类型 |
index |
值为索引类型 |
auto |
设备先按名称类型进行匹配,如果没有匹配到任何信息,再按照索引类型进行匹配 如果不指定valuetype的属性,缺省使用auto |
下面以IfIndex列值为index类型,值为1为例:
Path *path01 = request.mutable_prefix();
PathElem *pathelem01 = path01->add_elem();
pathelem01->set_name("VLAN");
PathElem *pathelem02 = path01->add_elem();
pathelem02->set_name("TrunkInterfaces");
/* SetRequest->delete */
Path *path02 = request.add_delete_();
PathElem *pathelem04 = path02->add_elem();
pathelem04->set_name("Interface ");
(*pathelem04->mutable_key())["IfIndex"] = "1";
(*pathelem04->mutable_key())["IfIndex#valuetype"] = "index";
gNMI Subscribe支持的属性如下:
· gNMI Get操作支持的所有属性,请参见“gNMI Get操作支持的属性”
这些属性可用于周期式上报的数据表的订阅。
· condition(数据推送条件)
该属性用于事件表的订阅,可选择的属性值包括:
¡ more:大于
¡ less:小于
¡ notLess:不小于
¡ notMore:不大于
¡ equal:等于
¡ notEqual:不等于
¡ include:包含
¡ exclude:不包含
¡ startWith:开始于
¡ endWith:结束于
如下示例用于监控LLDP事件:
auto prefix = subscribeList->mutable_prefix();
auto pathelem01 = prefix->add_elem();
pathelem01->set_name("LLDP");
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path();
auto pathelem02 = path->add_elem();
pathelem02->set_name("NeighborEvent");
auto pathelem03 = path->add_elem();
pathelem03->set_name("Neighbor");
(*pathelem03->mutable_key())["IfName"] = "Ten-GigabitEthernet3/0/1";
(*pathelem03->mutable_key())["IfName#condition"] = "equal";
· match(条件匹配)
该属性用于数据表的订阅,有以下两种使用方法:
¡ 与固定值进行条件匹配:支持的匹配命令如表2-3所示。
¡ 与其他列数据进行条件匹配(仅触发式上报的数据表支持)。可选择的属性值包括:
- refMore:大于
- refLess:小于
- refNotLess:不小于
- refNotMore:不大于
- refEqual:等于
- refNotEqual:不等于
下面以current-usage列值大于alarm-threshold列值触发条件为例:
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path();
auto pathelem02 = path->add_elem();
pathelem02->set_name("queue");
auto pathelem03 = path->add_elem();
pathelem03->set_name("state");
(*pathelem03->mutable_key())["current-usage#match"] = "refMore:alarm-threshold";
· select
该属性用于在触发式上报的数据表的订阅中选择列,表示仅订阅指定的列数据。
下面以订阅current-usage和available-usage为例:
auto subscribe = subscribeList->add_subscription();
auto path = subscribe->mutable_path();
auto pathelem02 = path->add_elem();
pathelem02->set_name("queue");
auto pathelem03 = path->add_elem();
pathelem03->set_name("state");
(*pathelem03->mutable_key())["current-usage#select"] = "";
(*pathelem03->mutable_key())["available-usage#select"] = "";
gNMI类操作对象包括数据表和事件表:
· 数据表有两种类型:周期式上报和触发式上报。
· 事件表均为触发式上报。
不同款型规格的资料略有差异, 详细信息请向具体销售和400咨询。H3C保留在没有任何通知或提示的情况下对资料内容进行修改的权利!